@naparnik/mcp 0.10.22 → 0.10.38

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,11 +1,11 @@
1
- import { createHash, randomBytes, randomUUID } from 'crypto';
2
1
  import { execFile, spawn, execFileSync } from 'child_process';
3
- import { existsSync, readFileSync, closeSync, mkdirSync, openSync, readdirSync, statSync, rmSync, writeFileSync, renameSync, symlinkSync, chmodSync, lstatSync, realpathSync, mkdtempSync, linkSync, readlinkSync, appendFileSync, fsyncSync } from 'fs';
4
- import { platform, homedir, tmpdir } from 'os';
5
- import { win32, join, dirname, sep, resolve, relative, isAbsolute, basename } from 'path';
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
+ import { platform, homedir, arch, release, userInfo, tmpdir } from 'os';
5
+ import { win32, join, dirname, sep, resolve, relative, isAbsolute, basename, extname } from 'path';
6
6
  import { promisify } from 'util';
7
7
  import { gunzipSync } from 'zlib';
8
- import { pathToFileURL } from 'url';
8
+ import { pathToFileURL, fileURLToPath } from 'url';
9
9
  import { readFile } from 'fs/promises';
10
10
 
11
11
  var __defProp = Object.defineProperty;
@@ -176,12 +176,12 @@ var StdioServerTransport = class {
176
176
  throw new McpError(McpErrorCode.InternalError, "Stdio-\u0442\u0440\u0430\u043D\u0441\u043F\u043E\u0440\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D");
177
177
  }
178
178
  const line = encodeNdjsonLine(message);
179
- await new Promise((resolve4, reject) => {
179
+ await new Promise((resolve5, reject) => {
180
180
  let settled = false;
181
181
  const done = () => {
182
182
  if (settled) return;
183
183
  settled = true;
184
- resolve4();
184
+ resolve5();
185
185
  };
186
186
  const flushed = process.stdout.write(line, "utf8", (err) => {
187
187
  if (err !== null && err !== void 0) {
@@ -226,8 +226,8 @@ var StdioServerTransport = class {
226
226
  function toolSuccess(content, structured) {
227
227
  return { type: "success", content, ...structured !== void 0 ? { structured } : {} };
228
228
  }
229
- function toolMixed(blocks) {
230
- return { type: "success", content: blocks };
229
+ function toolMixed(blocks, structured) {
230
+ return { type: "success", content: blocks, ...structured !== void 0 ? { structured } : {} };
231
231
  }
232
232
  function toolError(error40, code) {
233
233
  return { type: "error", error: error40 };
@@ -412,6 +412,13 @@ var McpServer = class {
412
412
  * Обрабатывает запрос инициализации.
413
413
  */
414
414
  handleInitialize(params) {
415
+ const client = params?.["clientInfo"];
416
+ if (typeof client?.name === "string") {
417
+ this.toolContext = {
418
+ ...this.toolContext,
419
+ host: { name: client.name, version: typeof client.version === "string" ? client.version : "" }
420
+ };
421
+ }
415
422
  const clientVersion = params?.["protocolVersion"];
416
423
  const negotiatedVersion = typeof clientVersion === "string" && clientVersion.length > 0 ? clientVersion : MCP_PROTOCOL_VERSION;
417
424
  return {
@@ -658,8 +665,8 @@ function describeFailure(err) {
658
665
  const bottom = err.cause;
659
666
  if (bottom instanceof Error) {
660
667
  const code = bottom.code;
661
- const tail = typeof code === "string" ? `${code}: ${bottom.message}` : bottom.message;
662
- return err.message === tail || tail === "" ? err.message : `${err.message} (${tail})`;
668
+ const tail2 = typeof code === "string" ? `${code}: ${bottom.message}` : bottom.message;
669
+ return err.message === tail2 || tail2 === "" ? err.message : `${err.message} (${tail2})`;
663
670
  }
664
671
  return err.message;
665
672
  }
@@ -679,8 +686,8 @@ function loadConfig(env = process.env) {
679
686
  throw new NaparnikConfigError(state.message);
680
687
  }
681
688
  function fromHost(value) {
682
- const text = (value ?? "").trim();
683
- return /^\$\{[^}]*\}$/.test(text) ? "" : text;
689
+ const text2 = (value ?? "").trim();
690
+ return /^\$\{[^}]*\}$/.test(text2) ? "" : text2;
684
691
  }
685
692
  function readConfig(env = process.env) {
686
693
  const apiKey = fromHost(env["NAPARNIK_API_KEY"]);
@@ -720,9 +727,9 @@ function parsePositiveInt(raw, fallback) {
720
727
  }
721
728
 
722
729
  // src/version.ts
723
- var VERSION = "0.10.22";
730
+ var VERSION = "0.10.38";
724
731
  var USER_AGENT = `naparnik-mcp/${VERSION}`;
725
- var MIN_ENGINE = "2026.09.23-406";
732
+ var MIN_ENGINE = "2026.09.24-446";
726
733
  var LAUNCHER_PROTOCOL = 1;
727
734
 
728
735
  // src/contracts/sdk-headers.ts
@@ -890,9 +897,9 @@ var NaparnikApiClient = class {
890
897
  return headers;
891
898
  }
892
899
  async parse(response, shape = {}) {
893
- let text;
900
+ let text2;
894
901
  try {
895
- text = await response.text();
902
+ text2 = await response.text();
896
903
  } catch (err) {
897
904
  throw new NaparnikServerError(
898
905
  `\u0421\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u0438 \u0447\u0442\u0435\u043D\u0438\u0438 \u043E\u0442\u0432\u0435\u0442\u0430 \u041D\u0430\u043F\u0430\u0440\u043D\u0438\u043A\u0430: ${describe(err)}`,
@@ -900,15 +907,15 @@ var NaparnikApiClient = class {
900
907
  );
901
908
  }
902
909
  if (response.ok) {
903
- if (shape.wantText === true) return text;
904
- if (text === "") return {};
910
+ if (shape.wantText === true) return text2;
911
+ if (text2 === "") return {};
905
912
  try {
906
- return JSON.parse(text);
913
+ return JSON.parse(text2);
907
914
  } catch {
908
- throw new NaparnikServerError("\u041D\u0430\u043F\u0430\u0440\u043D\u0438\u043A \u0432\u0435\u0440\u043D\u0443\u043B \u043D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 JSON", response.status, truncate(text));
915
+ throw new NaparnikServerError("\u041D\u0430\u043F\u0430\u0440\u043D\u0438\u043A \u0432\u0435\u0440\u043D\u0443\u043B \u043D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 JSON", response.status, truncate(text2));
909
916
  }
910
917
  }
911
- const payload = safeJson(text);
918
+ const payload = safeJson(text2);
912
919
  switch (response.status) {
913
920
  // ⚠ ЗАПАСНЫЕ ТЕКСТЫ ЗДЕСЬ ВЕЛИ В ПАНЕЛЬ, КОТОРОЙ НЕТ, и молча: обычно
914
921
  // сервер присылает свой текст, и эти строки видны только когда 401
@@ -940,20 +947,20 @@ var NaparnikApiClient = class {
940
947
  throw new NaparnikServerError(
941
948
  `\u041D\u0430\u043F\u0430\u0440\u043D\u0438\u043A \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439 ${response.status}. \u042D\u0442\u043E \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0439 \u0441\u0431\u043E\u0439 \u043D\u0430 \u043D\u0430\u0448\u0435\u0439 \u0441\u0442\u043E\u0440\u043E\u043D\u0435.`,
942
949
  response.status,
943
- truncate(text)
950
+ truncate(text2)
944
951
  );
945
952
  }
946
953
  throw new NaparnikClientError(
947
954
  messageFrom(payload) ?? `\u0417\u0430\u043F\u0440\u043E\u0441 \u043E\u0442\u043A\u043B\u043E\u043D\u0451\u043D (${response.status}).`,
948
955
  response.status,
949
- truncate(text)
956
+ truncate(text2)
950
957
  );
951
958
  }
952
959
  }
953
960
  };
954
- function safeJson(text) {
961
+ function safeJson(text2) {
955
962
  try {
956
- return JSON.parse(text);
963
+ return JSON.parse(text2);
957
964
  } catch {
958
965
  return null;
959
966
  }
@@ -970,17 +977,17 @@ function messageFrom(payload) {
970
977
  function numberOr(value, fallback) {
971
978
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
972
979
  }
973
- function truncate(text, limit = 500) {
974
- return text.length > limit ? `${text.slice(0, limit)}\u2026` : text;
980
+ function truncate(text2, limit = 500) {
981
+ return text2.length > limit ? `${text2.slice(0, limit)}\u2026` : text2;
975
982
  }
976
983
  function networkHint(err) {
977
- const text = describeFailure(err);
984
+ const text2 = describeFailure(err);
978
985
  const proxy = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"].find((i) => (process.env[i] ?? "").trim() !== "");
979
986
  if (proxy !== void 0) {
980
987
  return `
981
988
  \u041D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u0437\u0430\u0434\u0430\u043D ${proxy}, \u043D\u043E Node \u043D\u0435 \u0445\u043E\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u043F\u0440\u043E\u043A\u0441\u0438 \u0441\u0430\u043C \u2014 \u0437\u0430\u043F\u0440\u043E\u0441 \u0443\u0448\u0451\u043B \u043D\u0430\u043F\u0440\u044F\u043C\u0443\u044E. \u0415\u0441\u043B\u0438 \u0432\u044B\u0445\u043E\u0434 \u0432 \u0438\u043D\u0442\u0435\u0440\u043D\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0435\u0440\u0435\u0437 \u043F\u0440\u043E\u043A\u0441\u0438, \u043D\u0443\u0436\u0435\u043D Node 24 \u0438\u043B\u0438 \u043D\u043E\u0432\u0435\u0435 \u0441 NODE_USE_ENV_PROXY=1.`;
982
989
  }
983
- if (/CERT|SELF_SIGNED|UNABLE_TO_VERIFY/i.test(text)) {
990
+ if (/CERT|SELF_SIGNED|UNABLE_TO_VERIFY/i.test(text2)) {
984
991
  return "\n\u041F\u043E\u0445\u043E\u0436\u0435 \u043D\u0430 \u043F\u0435\u0440\u0435\u0445\u0432\u0430\u0442 TLS (\u043A\u043E\u0440\u043F\u043E\u0440\u0430\u0442\u0438\u0432\u043D\u044B\u0439 \u0430\u043D\u0442\u0438\u0432\u0438\u0440\u0443\u0441 \u0438\u043B\u0438 \u043F\u0440\u043E\u043A\u0441\u0438). \u041F\u0443\u0442\u044C \u043A \u043A\u043E\u0440\u043D\u0435\u0432\u043E\u043C\u0443 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u0443 \u043E\u0440\u0433\u0430\u043D\u0438\u0437\u0430\u0446\u0438\u0438 \u0437\u0430\u0434\u0430\u0451\u0442\u0441\u044F \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0439 NODE_EXTRA_CA_CERTS.";
985
992
  }
986
993
  return "";
@@ -989,7 +996,175 @@ function describe(err) {
989
996
  return describeFailure(err);
990
997
  }
991
998
  function sleep(ms) {
992
- return new Promise((resolve4) => setTimeout(resolve4, ms));
999
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
1000
+ }
1001
+ var TAIL_MS = 10 * 6e4;
1002
+ 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.";
1003
+ var realClock = {
1004
+ now: () => Date.now(),
1005
+ later(ms, fire) {
1006
+ const timer = setTimeout(fire, ms);
1007
+ timer.unref();
1008
+ return () => clearTimeout(timer);
1009
+ }
1010
+ };
1011
+ var Wakefulness = class {
1012
+ constructor(blocker, opts = {}) {
1013
+ this.blocker = blocker;
1014
+ this.clock = opts.clock ?? realClock;
1015
+ this.pid = opts.pid ?? process.pid;
1016
+ this.tailMs = opts.tailMs ?? TAIL_MS;
1017
+ }
1018
+ blocker;
1019
+ holders = /* @__PURE__ */ new Set();
1020
+ until = 0;
1021
+ lamp = null;
1022
+ /**
1023
+ * Система однажды лампу не дала — больше не просим. Иначе каждый вызов
1024
+ * пытался бы заново запустить то, чего нет, а работа человека важнее.
1025
+ */
1026
+ refused = false;
1027
+ cancelTail = null;
1028
+ clock;
1029
+ pid;
1030
+ tailMs;
1031
+ /** Держать компьютер бодрым; вернуть снятие. Снять второй раз — ничего не делает. */
1032
+ hold() {
1033
+ const token = /* @__PURE__ */ Symbol("hold");
1034
+ this.holders.add(token);
1035
+ this.sync();
1036
+ return () => {
1037
+ if (!this.holders.delete(token)) return;
1038
+ this.until = Math.max(this.until, this.clock.now() + this.tailMs);
1039
+ this.sync();
1040
+ };
1041
+ }
1042
+ /**
1043
+ * Держать, пока идёт работа, — и когда она кончилась успехом, и когда упала.
1044
+ *
1045
+ * ⚠ Работа — функцией, а не готовым обещанием. Обещание уже запущено: тело
1046
+ * async-функции до первого await идёт при вычислении аргумента, и начало
1047
+ * работы оставалось без лампы (тест обёртки поймал ровно это).
1048
+ */
1049
+ async during(work) {
1050
+ const release2 = this.hold();
1051
+ try {
1052
+ return await work();
1053
+ } finally {
1054
+ release2();
1055
+ }
1056
+ }
1057
+ /**
1058
+ * Своя лампа для фоновой работы, привязанная к её процессу. Гаснет сама, когда
1059
+ * работа кончилась, — даже если нас к тому времени уже закрыли.
1060
+ */
1061
+ follow(pid) {
1062
+ if (this.refused) return;
1063
+ try {
1064
+ if (this.blocker(pid) === null) this.refused = true;
1065
+ } catch {
1066
+ this.refused = true;
1067
+ }
1068
+ }
1069
+ sync() {
1070
+ const want = this.holders.size > 0 || this.clock.now() < this.until;
1071
+ if (want && this.lamp === null && !this.refused) this.light();
1072
+ if (!want && this.lamp !== null) {
1073
+ this.lamp.stop();
1074
+ this.lamp = null;
1075
+ }
1076
+ this.cancelTail?.();
1077
+ this.cancelTail = null;
1078
+ if (this.holders.size === 0 && want) {
1079
+ this.cancelTail = this.clock.later(this.until - this.clock.now(), () => this.sync());
1080
+ }
1081
+ }
1082
+ light() {
1083
+ let lamp;
1084
+ try {
1085
+ lamp = this.blocker(this.pid);
1086
+ } catch {
1087
+ lamp = null;
1088
+ }
1089
+ if (lamp === null) {
1090
+ this.refused = true;
1091
+ return;
1092
+ }
1093
+ this.lamp = lamp;
1094
+ lamp.onExit(() => {
1095
+ if (this.lamp === lamp) this.lamp = null;
1096
+ });
1097
+ }
1098
+ };
1099
+ var ASLEEP = new Wakefulness(() => null);
1100
+ function systemBlocker(platform4 = process.platform, run = spawn, env = process.env) {
1101
+ return (pid) => {
1102
+ let command;
1103
+ let args;
1104
+ if (platform4 === "darwin") {
1105
+ command = "/usr/bin/caffeinate";
1106
+ args = ["-i", "-w", String(pid)];
1107
+ } else if (platform4 === "win32") {
1108
+ command = `${env["SystemRoot"] ?? "C:\\Windows"}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
1109
+ args = [
1110
+ "-NoProfile",
1111
+ "-NonInteractive",
1112
+ "-WindowStyle",
1113
+ "Hidden",
1114
+ // Закодированным, а не строкой: в тексте кавычки из C#, и Windows
1115
+ // склеивает их с кавычками командной строки непредсказуемо.
1116
+ "-EncodedCommand",
1117
+ Buffer.from(windowsScript(pid), "utf16le").toString("base64")
1118
+ ];
1119
+ } else {
1120
+ return null;
1121
+ }
1122
+ const child = run(command, args, {
1123
+ stdio: "ignore",
1124
+ windowsHide: true,
1125
+ detached: false
1126
+ });
1127
+ child.unref();
1128
+ let exited = false;
1129
+ const listeners = [];
1130
+ const gone = () => {
1131
+ if (exited) return;
1132
+ exited = true;
1133
+ for (const cb of listeners) cb();
1134
+ };
1135
+ child.on("error", gone);
1136
+ child.on("exit", gone);
1137
+ return {
1138
+ stop() {
1139
+ try {
1140
+ child.kill();
1141
+ } catch {
1142
+ }
1143
+ },
1144
+ onExit(cb) {
1145
+ if (exited) cb();
1146
+ else listeners.push(cb);
1147
+ }
1148
+ };
1149
+ };
1150
+ }
1151
+ function windowsScript(pid) {
1152
+ return [
1153
+ `$t = Add-Type -Name Power -Namespace Naparnik -PassThru -MemberDefinition '[DllImport("kernel32.dll")] public static extern uint SetThreadExecutionState(uint f);'`,
1154
+ "[void]$t::SetThreadExecutionState([uint32]2147483649)",
1155
+ `Wait-Process -Id ${pid} -ErrorAction SilentlyContinue`
1156
+ ].join("\n");
1157
+ }
1158
+ function withWakefulness(awake) {
1159
+ return (tool) => {
1160
+ const original = tool.execute.bind(tool);
1161
+ return {
1162
+ ...tool,
1163
+ async execute(input, ...rest) {
1164
+ return awake.during(() => original(input, ...rest));
1165
+ }
1166
+ };
1167
+ };
993
1168
  }
994
1169
  var FINGERPRINT_VERSION = "mid1";
995
1170
  var MIN_ID_LENGTH = 8;
@@ -1185,10 +1360,12 @@ function montageEnv(base, key) {
1185
1360
  };
1186
1361
  }
1187
1362
  var Engine = class {
1188
- constructor(env = process.env) {
1363
+ constructor(env = process.env, awake = ASLEEP) {
1189
1364
  this.env = env;
1365
+ this.awake = awake;
1190
1366
  }
1191
1367
  env;
1368
+ awake;
1192
1369
  passportCache;
1193
1370
  versionAtStart;
1194
1371
  pythonChecked;
@@ -1576,6 +1753,7 @@ var Engine = class {
1576
1753
  } catch {
1577
1754
  }
1578
1755
  }
1756
+ if (child.pid !== void 0) this.awake.follow(child.pid);
1579
1757
  this.watch(child, opts.place, id, opts.advice, log.file);
1580
1758
  return { id };
1581
1759
  }
@@ -1627,7 +1805,7 @@ var Engine = class {
1627
1805
  });
1628
1806
  });
1629
1807
  child.on("close", (code, signal) => {
1630
- const tail = logTail();
1808
+ const tail2 = logTail();
1631
1809
  if (signal) {
1632
1810
  void this.run(place, [
1633
1811
  "job-abort",
@@ -1635,17 +1813,17 @@ var Engine = class {
1635
1813
  "--signal",
1636
1814
  String(signal),
1637
1815
  "--tail",
1638
- lastLines(tail)
1816
+ lastLines(tail2)
1639
1817
  ]).catch(() => {
1640
1818
  });
1641
1819
  return;
1642
1820
  }
1643
- if (code === 0 || code === 2 || code === 3 || tail.trim() === "") return;
1821
+ if (code === 0 || code === 2 || code === 3 || tail2.trim() === "") return;
1644
1822
  void this.run(place, [
1645
1823
  "job-fail",
1646
1824
  id,
1647
1825
  "--what",
1648
- lastLines(tail),
1826
+ lastLines(tail2),
1649
1827
  "--what-to-do",
1650
1828
  "\u043F\u043E\u043A\u0430\u0436\u0438 \u044D\u0442\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u0446\u0435\u043B\u0438\u043A\u043E\u043C"
1651
1829
  ]).catch(() => {
@@ -1689,15 +1867,14 @@ function parseFailure(err, fallback) {
1689
1867
  return { ok: false, text: mine !== "" ? mine : e?.message ?? fallback };
1690
1868
  }
1691
1869
  function details(err) {
1692
- const e = err;
1693
- return [e?.stdout, e?.stderr, e?.message].filter(Boolean).join("\n").trim();
1870
+ return parseFailure(err, "").text.trim();
1694
1871
  }
1695
1872
  function lastLine(output) {
1696
1873
  const lines = output.trim().split("\n");
1697
1874
  return (lines[lines.length - 1] ?? "").trim();
1698
1875
  }
1699
- function lastLines(text, howMany) {
1700
- return text.trim().split("\n").slice(-8).join("\n");
1876
+ function lastLines(text2, howMany) {
1877
+ return text2.trim().split("\n").slice(-8).join("\n");
1701
1878
  }
1702
1879
  var NOT_JSON = "\u0414\u0432\u0438\u0436\u043E\u043A \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043D\u0435 \u0442\u0435\u043C, \u0447\u0435\u0433\u043E \u0436\u0434\u0430\u043B\u0438. \u041E\u0431\u043D\u043E\u0432\u0438 \u0434\u0432\u0438\u0436\u043E\u043A \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C setup \u0441 what=engine \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438.";
1703
1880
 
@@ -13301,19 +13478,19 @@ function acquireVersionLock(dir, hooks = {}) {
13301
13478
  };
13302
13479
  }
13303
13480
  async function withVersionLock(dir, work, hooks = {}) {
13304
- const release = acquireVersionLock(dir, hooks);
13481
+ const release2 = acquireVersionLock(dir, hooks);
13305
13482
  try {
13306
13483
  return await work();
13307
13484
  } finally {
13308
- release();
13485
+ release2();
13309
13486
  }
13310
13487
  }
13311
13488
  function withVersionLockSync(dir, work) {
13312
- const release = acquireVersionLock(dir);
13489
+ const release2 = acquireVersionLock(dir);
13313
13490
  try {
13314
13491
  return work();
13315
13492
  } finally {
13316
- release();
13493
+ release2();
13317
13494
  }
13318
13495
  }
13319
13496
  var PUBLICATION_FILE = ".publication.json";
@@ -13922,7 +14099,7 @@ function preparedCandidateAfterBootstrap(engine, before) {
13922
14099
  }
13923
14100
  return { ok: true, candidate, place };
13924
14101
  }
13925
- function blockCandidate(dir, candidate, text) {
14102
+ function blockCandidate(dir, candidate, text2) {
13926
14103
  if (!sameCandidateState(readCandidate(dir), candidate)) {
13927
14104
  return {
13928
14105
  ok: false,
@@ -13930,12 +14107,20 @@ function blockCandidate(dir, candidate, text) {
13930
14107
  };
13931
14108
  }
13932
14109
  writeCandidate(dir, { ...candidate, state: "blocked" });
14110
+ const lead = candidate.baseCurrent === null ? `\u0412\u0435\u0440\u0441\u0438\u044F ${candidate.version} \u043D\u0435 \u0432\u0441\u0442\u0430\u043B\u0430.` : `\u041D\u043E\u0432\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F ${candidate.version} \u043D\u0435 \u0432\u0441\u0442\u0430\u043B\u0430 \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043F\u0440\u0435\u0436\u043D\u044F\u044F ${candidate.baseCurrent}, \u043C\u043E\u043D\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043C\u043E\u0436\u043D\u043E.`;
13933
14111
  return {
13934
14112
  ok: false,
13935
- text: text.includes("\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C:") ? text : `${text}
13936
- \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u0438\u0441\u043F\u0440\u0430\u0432\u044C\u0442\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u0443\u044E \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 setup.`
14113
+ text: text2.includes("\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C:") ? `${lead}
14114
+ ${text2}` : `${lead}
14115
+ ${text2}
14116
+ \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.`
13937
14117
  };
13938
14118
  }
14119
+ function commandWords(trouble) {
14120
+ const text2 = parseFailure(trouble, describeFailure(trouble)).text;
14121
+ if (!text2.includes("Traceback (most recent call last):")) return text2;
14122
+ return lastLine(text2);
14123
+ }
13939
14124
  function sameCandidateState(actual, expected) {
13940
14125
  if (actual === null) return false;
13941
14126
  const samePayload = actual.payload === void 0 && expected.payload === void 0 || actual.payload !== void 0 && expected.payload !== void 0 && samePayloadProof(actual.payload, expected.payload);
@@ -13986,9 +14171,9 @@ async function prepareCopiedCandidateVenv(engine, dir, version2, place) {
13986
14171
  if (engine.prepareCandidateVenv !== void 0) {
13987
14172
  await engine.prepareCandidateVenv(place);
13988
14173
  } else {
13989
- const { execFile: execFile3 } = await import('child_process');
14174
+ const { execFile: execFile4 } = await import('child_process');
13990
14175
  const { promisify: promisify3 } = await import('util');
13991
- await promisify3(execFile3)(engine.bootstrapPython(), ["-m", "venv", "--copies", venv], {
14176
+ await promisify3(execFile4)(engine.bootstrapPython(), ["-m", "venv", "--copies", venv], {
13992
14177
  timeout: 10 * 6e4,
13993
14178
  encoding: "utf8",
13994
14179
  env: { ...engine.pythonEnv(), PYTHONDONTWRITEBYTECODE: "1" }
@@ -14004,7 +14189,7 @@ async function prepareCopiedCandidateVenv(engine, dir, version2, place) {
14004
14189
  }
14005
14190
  async function candidatePipReady(engine, python) {
14006
14191
  if (engine.candidatePipReady !== void 0) return engine.candidatePipReady(python);
14007
- const { execFile: execFile3 } = await import('child_process');
14192
+ const { execFile: execFile4 } = await import('child_process');
14008
14193
  const { promisify: promisify3 } = await import('util');
14009
14194
  const env = { ...engine.pythonEnv(), PYTHONDONTWRITEBYTECODE: "1" };
14010
14195
  delete env["PYTHONHOME"];
@@ -14012,7 +14197,7 @@ async function candidatePipReady(engine, python) {
14012
14197
  delete env["VIRTUAL_ENV"];
14013
14198
  delete env["__PYVENV_LAUNCHER__"];
14014
14199
  try {
14015
- await promisify3(execFile3)(python, ["-B", "-m", "pip", "--version"], {
14200
+ await promisify3(execFile4)(python, ["-B", "-m", "pip", "--version"], {
14016
14201
  timeout: 6e4,
14017
14202
  encoding: "utf8",
14018
14203
  env
@@ -14119,7 +14304,7 @@ async function activatePreparedCandidateLocked(engine, manifest, trial, extras =
14119
14304
  if (installedVenvTrouble !== null) throw new Error(installedVenvTrouble);
14120
14305
  } catch (trouble) {
14121
14306
  return blockCandidate(dir, candidate, `\u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u043D\u0435 \u0433\u043E\u0442\u043E\u0432\u043E \u2014 current \u043D\u0435 \u043C\u0435\u043D\u044F\u043B.
14122
- ${describeFailure(trouble)}`);
14307
+ ${commandWords(trouble)}`);
14123
14308
  }
14124
14309
  }
14125
14310
  const python = engine.candidatePython(place);
@@ -14163,7 +14348,7 @@ ${smoke}`);
14163
14348
  dir,
14164
14349
  candidate,
14165
14350
  `\u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u0441\u0440\u0435\u0434\u0430 \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u043D\u0435 \u0433\u043E\u0442\u043E\u0432\u0430 \u2014 current \u0438 \u043E\u0431\u0449\u0438\u0435 \u0440\u0430\u0431\u043E\u0447\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044B \u043D\u0435 \u043C\u0435\u043D\u044F\u043B.
14166
- ${describeFailure(trouble)}`
14351
+ ${commandWords(trouble)}`
14167
14352
  );
14168
14353
  }
14169
14354
  const firstCompatibility = await compatibilityProof(engine, dir, candidate.version, python, place);
@@ -14324,9 +14509,9 @@ async function dryRun(engine, build, expected, trial) {
14324
14509
  }
14325
14510
  }
14326
14511
  async function probe(engine, consolePy) {
14327
- const { execFile: execFile3 } = await import('child_process');
14512
+ const { execFile: execFile4 } = await import('child_process');
14328
14513
  const { promisify: promisify3 } = await import('util');
14329
- return promisify3(execFile3)(engine.enginePython(), [consolePy, "version"], {
14514
+ return promisify3(execFile4)(engine.enginePython(), [consolePy, "version"], {
14330
14515
  timeout: 6e4,
14331
14516
  encoding: "utf8",
14332
14517
  env: engine.pythonEnv()
@@ -14508,8 +14693,8 @@ function fieldLine(header, start, length) {
14508
14693
  return chunk.subarray(0, end === -1 ? chunk.length : end).toString("utf8");
14509
14694
  }
14510
14695
  function octal(header, start, length) {
14511
- const text = fieldLine(header, start, length).trim();
14512
- const ch = Number.parseInt(text, 8);
14696
+ const text2 = fieldLine(header, start, length).trim();
14697
+ const ch = Number.parseInt(text2, 8);
14513
14698
  return Number.isFinite(ch) ? ch : 0;
14514
14699
  }
14515
14700
  function fieldSize(header, start, length) {
@@ -14769,7 +14954,7 @@ var probeShell = async (entry) => {
14769
14954
  encoding: "utf8",
14770
14955
  env
14771
14956
  });
14772
- await new Promise((resolve4, reject) => {
14957
+ await new Promise((resolve5, reject) => {
14773
14958
  const child = spawn(process.execPath, [entry], { stdio: ["pipe", "pipe", "pipe"], env });
14774
14959
  let output = "";
14775
14960
  let errors = "";
@@ -14780,7 +14965,7 @@ var probeShell = async (entry) => {
14780
14965
  finished = true;
14781
14966
  clearTimeout(timer);
14782
14967
  child.kill();
14783
- if (trouble === void 0) resolve4();
14968
+ if (trouble === void 0) resolve5();
14784
14969
  else reject(trouble);
14785
14970
  };
14786
14971
  const send = (message) => {
@@ -15116,10 +15301,10 @@ function recoverInterruptedPublication(dir) {
15116
15301
  \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.`
15117
15302
  };
15118
15303
  }
15119
- function platformKey(os = process.platform, arch = process.arch) {
15120
- if (os === "darwin") return arch === "arm64" ? "Darwin-arm64" : arch === "x64" ? "Darwin-x86_64" : null;
15121
- if (os === "win32") return arch === "x64" ? "Windows-AMD64" : null;
15122
- if (os === "linux") return arch === "x64" ? "Linux-x86_64" : arch === "arm64" ? "Linux-aarch64" : null;
15304
+ function platformKey(os = process.platform, arch2 = process.arch) {
15305
+ if (os === "darwin") return arch2 === "arm64" ? "Darwin-arm64" : arch2 === "x64" ? "Darwin-x86_64" : null;
15306
+ if (os === "win32") return arch2 === "x64" ? "Windows-AMD64" : null;
15307
+ if (os === "linux") return arch2 === "x64" ? "Linux-x86_64" : arch2 === "arm64" ? "Linux-aarch64" : null;
15123
15308
  return null;
15124
15309
  }
15125
15310
  function pythonSource(engineDir, key = platformKey()) {
@@ -15382,9 +15567,244 @@ function safePythonDirectory(home, pythonDir) {
15382
15567
  }
15383
15568
  }
15384
15569
  function troubleText(what, trouble) {
15385
- const tail = trouble instanceof DeliveryError ? trouble.message : trouble instanceof Error ? trouble.message : String(trouble);
15570
+ const tail2 = trouble instanceof DeliveryError ? trouble.message : trouble instanceof Error ? trouble.message : String(trouble);
15386
15571
  return `${what}, \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043C\u0435\u043D\u044F\u043B.
15387
- ${tail}` + (tail.includes("\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C:") ? "" : "\n\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u0438\u0441\u043F\u0440\u0430\u0432\u044C\u0442\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u0443\u044E \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 setup.");
15572
+ ${tail2}` + (tail2.includes("\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C:") ? "" : "\n\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u0438\u0441\u043F\u0440\u0430\u0432\u044C\u0442\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u0443\u044E \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 setup.");
15573
+ }
15574
+
15575
+ // src/progress.ts
15576
+ var realPace = {
15577
+ now: () => Date.now(),
15578
+ rest: (ms) => new Promise((done) => {
15579
+ setTimeout(done, ms);
15580
+ })
15581
+ };
15582
+ var WAIT_LIMIT_SECONDS = 55;
15583
+ var POLL_EVERY_MS = 2e3;
15584
+ var JOB_STILL_COMPUTING = "\u0415\u0449\u0451 \u0441\u0447\u0438\u0442\u0430\u0435\u0442";
15585
+ var JOB_IN_QUEUE = "\u0416\u0434\u0451\u0442 \u043E\u0447\u0435\u0440\u0435\u0434\u0438";
15586
+ function stillWorking(text2) {
15587
+ return text2.includes(JOB_STILL_COMPUTING) || text2.includes(JOB_IN_QUEUE);
15588
+ }
15589
+ function waitSeconds(asked) {
15590
+ if (asked === void 0 || !Number.isFinite(asked) || asked <= 0) return 0;
15591
+ return Math.min(Math.round(asked), WAIT_LIMIT_SECONDS);
15592
+ }
15593
+ function freshLines(text2, told) {
15594
+ const news = [];
15595
+ for (const row of text2.split("\n").slice(1)) {
15596
+ const line = row.trim();
15597
+ if (line.length === 0) continue;
15598
+ if (line.startsWith(JOB_STILL_COMPUTING)) continue;
15599
+ if (told.has(line)) continue;
15600
+ told.add(line);
15601
+ news.push(line);
15602
+ }
15603
+ return news;
15604
+ }
15605
+ async function followJob(opts) {
15606
+ const pace = opts.pace ?? realPace;
15607
+ const until = pace.now() + opts.seconds * 1e3;
15608
+ const told = /* @__PURE__ */ new Set();
15609
+ let state = await opts.ask();
15610
+ retell(state, told, opts.tell);
15611
+ while (state.ok && stillWorking(state.text) && pace.now() < until) {
15612
+ await pace.rest(POLL_EVERY_MS);
15613
+ try {
15614
+ state = await opts.ask();
15615
+ } catch {
15616
+ continue;
15617
+ }
15618
+ retell(state, told, opts.tell);
15619
+ }
15620
+ return state;
15621
+ }
15622
+ function retell(state, told, tell) {
15623
+ if (tell === void 0) return;
15624
+ for (const line of freshLines(state.text, told)) tell(line);
15625
+ }
15626
+ var PAUSES_LADDER = [
15627
+ "\u0423\u0431\u0435\u0440\u0443 \u043C\u043E\u043B\u0447\u0430\u043D\u0438\u0435 \u0438 \u0437\u0430\u043F\u0438\u043D\u043A\u0438 \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u044E.",
15628
+ "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u044B, \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0438 \u043F\u043E\u0432\u0435\u0440\u0445 \u0438 \u0437\u0432\u0443\u043A \u2014 \u044D\u0442\u043E \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430, \u0435\u0451 \u0437\u0434\u0435\u0441\u044C \u043D\u0435\u0442."
15629
+ ];
15630
+ var PAYLOAD_LADDER = [
15631
+ "\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u043F\u043E\u043B\u043E\u0436\u0443 \u0434\u0432\u0438\u0436\u043E\u043A \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u043E\u0434\u043D\u043E\u0439 \u043F\u0430\u043F\u043A\u043E\u0439 \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.",
15632
+ "\u041F\u043E\u0442\u043E\u043C ffmpeg, python \u0438 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043C\u043E\u043D\u0442\u0430\u0436\u0430. \u0422\u044F\u0436\u0451\u043B\u043E\u0435 \u2014 \u0440\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, \u0447\u0438\u0441\u0442\u043A\u0430 \u0433\u043E\u043B\u043E\u0441\u0430 \u2014 \u0441\u0442\u0430\u0432\u0438\u0442\u0441\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E \u043F\u0440\u043E\u0441\u044C\u0431\u0435."
15633
+ ];
15634
+ var INSTALL_WORDS = {
15635
+ ffmpeg: "\u043F\u043E\u0441\u0442\u0430\u0432\u043B\u044E ffmpeg \u2014 \u0438\u043C \u0441\u043E\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044F \u0432\u0438\u0434\u0435\u043E",
15636
+ python: "\u043F\u043E\u0441\u0442\u0430\u0432\u043B\u044E python",
15637
+ env: "\u0441\u043E\u0431\u0435\u0440\u0443 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043C\u043E\u043D\u0442\u0430\u0436\u0430",
15638
+ model: "\u0441\u043A\u0430\u0447\u0430\u044E \u043C\u043E\u0434\u0435\u043B\u044C \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u044F \u0440\u0435\u0447\u0438 \u2014 \u043E\u043D\u0430 \u0438\u0437 \u0432\u0441\u0435\u0433\u043E \u0441\u0430\u043C\u0430\u044F \u0442\u044F\u0436\u0451\u043B\u0430\u044F",
15639
+ cutout: "\u0441\u043A\u0430\u0447\u0430\u044E \u0442\u043E, \u0447\u0435\u043C \u043E\u0442\u0434\u0435\u043B\u044F\u044E \u0433\u0435\u0440\u043E\u044F \u043E\u0442 \u0444\u043E\u043D\u0430",
15640
+ voice: "\u0441\u043A\u0430\u0447\u0430\u044E \u0442\u043E, \u0447\u0435\u043C \u0447\u0438\u0449\u0443 \u0433\u043E\u043B\u043E\u0441",
15641
+ browser: "\u043F\u043E\u0441\u0442\u0430\u0432\u043B\u044E \u0431\u0440\u0430\u0443\u0437\u0435\u0440, \u043A\u043E\u0442\u043E\u0440\u044B\u043C \u0440\u0438\u0441\u0443\u044E \u0433\u0440\u0430\u0444\u0438\u043A\u0443"
15642
+ };
15643
+ function installLadder(step) {
15644
+ if (step === "all") {
15645
+ return [
15646
+ "\u041F\u0440\u043E\u0432\u0435\u0440\u044E, \u0447\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442, \u0438 \u0434\u043E\u0441\u0442\u0430\u0432\u043B\u044E \u043F\u043E \u043E\u0447\u0435\u0440\u0435\u0434\u0438.",
15647
+ "\u0422\u044F\u0436\u0451\u043B\u043E\u0435 \u0441\u044E\u0434\u0430 \u043D\u0435 \u0432\u0445\u043E\u0434\u0438\u0442: \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0447\u0438 \u0438 \u0432\u044B\u0440\u0435\u0437 \u0433\u0435\u0440\u043E\u044F \u0441\u0442\u0430\u0432\u044F\u0442\u0441\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \xAB\u0434\u0430\xBB \u043E\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430."
15648
+ ];
15649
+ }
15650
+ const word = INSTALL_WORDS[step];
15651
+ if (word === void 0) return [];
15652
+ return [
15653
+ `\u0421\u0435\u0439\u0447\u0430\u0441 ${word}.`,
15654
+ "\u0418\u0434\u0451\u0442 \u043C\u0438\u043D\u0443\u0442\u0430\u043C\u0438. \u041E\u0431\u043E\u0440\u0432\u0451\u0442\u0441\u044F \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u0442\u043E\u0442 \u0436\u0435 \u0448\u0430\u0433: \u0443\u0436\u0435 \u0441\u043A\u0430\u0447\u0430\u043D\u043D\u043E\u0435 \u043D\u0435 \u043F\u0440\u043E\u043F\u0430\u0434\u0451\u0442."
15655
+ ];
15656
+ }
15657
+ var CALLS_FILE = "calls.jsonl";
15658
+ var CALLS_PREVIOUS = "calls.1.jsonl";
15659
+ var CALLS_CAP_BYTES = 1e6;
15660
+ function withCallLog(home, now = () => Date.now()) {
15661
+ return (tool) => {
15662
+ const original = tool.execute.bind(tool);
15663
+ return {
15664
+ ...tool,
15665
+ async execute(input, ...rest) {
15666
+ const started = now();
15667
+ const result = await original(input, ...rest);
15668
+ write(home, {
15669
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
15670
+ tool: tool.name,
15671
+ ok: result.type === "success",
15672
+ ms: now() - started
15673
+ });
15674
+ return result;
15675
+ }
15676
+ };
15677
+ };
15678
+ }
15679
+ function write(home, line) {
15680
+ try {
15681
+ mkdirSync(home, { recursive: true });
15682
+ const file2 = join(home, CALLS_FILE);
15683
+ rotate(file2, join(home, CALLS_PREVIOUS));
15684
+ appendFileSync(file2, `${JSON.stringify(line)}
15685
+ `, { encoding: "utf8", mode: 384 });
15686
+ } catch {
15687
+ }
15688
+ }
15689
+ function rotate(file2, previous) {
15690
+ try {
15691
+ if (statSync(file2).size >= CALLS_CAP_BYTES) renameSync(file2, previous);
15692
+ } catch {
15693
+ }
15694
+ }
15695
+
15696
+ // src/contracts/crash-report.ts
15697
+ var CRASH_REPORT_PATH = "/crash-report";
15698
+ var CRASH_CONTRACT = "naparnik-crash/1";
15699
+ var CRASH_LIMITS = {
15700
+ bodyBytes: 65536,
15701
+ errorType: 120,
15702
+ message: 500,
15703
+ trace: 8e3,
15704
+ stage: 120,
15705
+ osPlatform: 32,
15706
+ osRelease: 64,
15707
+ osArch: 16,
15708
+ hostName: 64,
15709
+ hostVersion: 32,
15710
+ shellVersion: 32,
15711
+ engineVersion: 64,
15712
+ pythonVersion: 16,
15713
+ ffmpegVersion: 64,
15714
+ recentCalls: 10,
15715
+ repeats: 1e5
15716
+ };
15717
+ var CRASH_TOOL_NAME = /^[a-z_]{1,64}$/;
15718
+ var CRASH_FINGERPRINT = /^[0-9a-f]{16}$/;
15719
+ var text = (max) => external_exports.string().max(max);
15720
+ var CrashReportRequest = external_exports.object({
15721
+ contract: external_exports.literal(CRASH_CONTRACT),
15722
+ occurred_at: external_exports.string().datetime({ offset: true }),
15723
+ where: external_exports.enum(["tool", "job", "process"]),
15724
+ tool: external_exports.string().regex(CRASH_TOOL_NAME).nullable(),
15725
+ stage: text(CRASH_LIMITS.stage).nullable(),
15726
+ error_type: external_exports.string().min(1).max(CRASH_LIMITS.errorType),
15727
+ message: text(CRASH_LIMITS.message),
15728
+ trace: text(CRASH_LIMITS.trace),
15729
+ fingerprint: external_exports.string().regex(CRASH_FINGERPRINT),
15730
+ repeats: external_exports.number().int().min(0).max(CRASH_LIMITS.repeats),
15731
+ os: external_exports.object({
15732
+ platform: text(CRASH_LIMITS.osPlatform),
15733
+ release: text(CRASH_LIMITS.osRelease),
15734
+ arch: text(CRASH_LIMITS.osArch)
15735
+ }).strict(),
15736
+ host: external_exports.object({
15737
+ name: text(CRASH_LIMITS.hostName),
15738
+ version: text(CRASH_LIMITS.hostVersion)
15739
+ }).strict().nullable(),
15740
+ versions: external_exports.object({
15741
+ shell: text(CRASH_LIMITS.shellVersion),
15742
+ engine: text(CRASH_LIMITS.engineVersion).nullable(),
15743
+ python: text(CRASH_LIMITS.pythonVersion).nullable(),
15744
+ ffmpeg: text(CRASH_LIMITS.ffmpegVersion).nullable()
15745
+ }).strict(),
15746
+ recent_calls: external_exports.array(external_exports.string().regex(CRASH_TOOL_NAME)).max(CRASH_LIMITS.recentCalls)
15747
+ }).strict();
15748
+ var SECRET_MIN = 4;
15749
+ var HUMAN_FILE = /[^\s"'«»“”/\\<>()]{0,255}\.(?:mp4|mov|m4v|mkv|avi|webm|mts|wav|mp3|m4a|aac|flac|ogg|jpg|jpeg|png|heic|gif|srt|vtt)\b/gi;
15750
+ var PATH = /(?:<naparnik>|<shell>|~|[A-Za-z]:)?(?:[\\/][^\s"'«»“”<>|,;()[\]{}\\/]+)+[\\/]?/g;
15751
+ var QUOTED = /(["'])([^"'\n]*)\1|«([^»\n]*)»|“([^”\n]*)”/g;
15752
+ var KEY = /npk_[A-Za-z0-9_-]{6,}/g;
15753
+ function scrub(text2, context) {
15754
+ let out = text2.replace(KEY, "<key>");
15755
+ for (const secret of secretForms(context.secrets)) {
15756
+ out = replaceAll(out, secret, "<video>");
15757
+ }
15758
+ out = replaceAll(out, context.shellDir, "<shell>");
15759
+ out = replaceAll(out, context.naparnikHome, "<naparnik>");
15760
+ out = replaceAll(out, context.home, "~");
15761
+ out = out.replace(/(?:\/Users|\/home|[A-Za-z]:\\Users|[A-Za-z]:\/Users)[\\/][^\s"'«»“”\\/]+/g, "~");
15762
+ out = out.replace(QUOTED, (whole, _q, plain, fir, curly) => {
15763
+ const inside2 = plain ?? fir ?? curly ?? "";
15764
+ if (!looksHuman(inside2)) return whole;
15765
+ const [open, close] = plain !== void 0 ? [_q, _q] : fir !== void 0 ? ["\xAB", "\xBB"] : ["\u201C", "\u201D"];
15766
+ return `${open}${looksLikePath(inside2) ? pathOrMark(inside2) : "<text>"}${close}`;
15767
+ });
15768
+ out = out.replace(PATH, (found) => pathOrMark(found));
15769
+ out = out.replace(HUMAN_FILE, "<file>");
15770
+ out = swallowSpacedTails(out);
15771
+ if (context.user.length >= 3) out = replaceAll(out, context.user, "<user>");
15772
+ return out;
15773
+ }
15774
+ function swallowSpacedTails(text2) {
15775
+ return text2.replace(/<(?:path|file)>[^\n:<>"'«»“”]{0,512}<(?:path|file)>/g, "<path>").replace(/(<(?:path|file)>)([^\n:<>"'«»“”]{0,512})/g, (whole, mark, tail2) => /[^\x00-\x7F]/.test(tail2) ? mark : whole);
15776
+ }
15777
+ function secretForms(secrets) {
15778
+ const forms = /* @__PURE__ */ new Set();
15779
+ for (const raw of secrets) {
15780
+ const value = raw.trim();
15781
+ if (value.length < SECRET_MIN) continue;
15782
+ forms.add(value);
15783
+ if (!looksLikePath(value)) continue;
15784
+ if (isAbsolute(value)) forms.add(resolve(value));
15785
+ const name = basename(value);
15786
+ const stem = name.slice(0, name.length - extname(name).length);
15787
+ for (const one of [name, stem, `${stem}.montage`]) {
15788
+ if (one.length >= SECRET_MIN) forms.add(one);
15789
+ }
15790
+ }
15791
+ return [...forms].sort((a, b) => b.length - a.length);
15792
+ }
15793
+ function pathOrMark(found) {
15794
+ const flat = found.replace(/\\/g, "/");
15795
+ const ours = /^(?:<naparnik>|~\/\.naparnik)\/(?:engine|shell|bin)(?:\/|$)/.test(flat) || flat.startsWith("<shell>") || /\/(?:site-packages|dist-packages|node_modules)\//.test(flat) || /\/lib\/python\d/i.test(flat) || /\/Python\d+\/Lib\//i.test(flat);
15796
+ return ours ? found : "<path>";
15797
+ }
15798
+ var HUMAN_FILE_ONE = new RegExp(HUMAN_FILE.source, "i");
15799
+ function looksLikePath(value) {
15800
+ return /[\\/]/.test(value) || HUMAN_FILE_ONE.test(value);
15801
+ }
15802
+ function looksHuman(inside2) {
15803
+ return /[^\x00-\x7F]/.test(inside2) || /\s/.test(inside2) || looksLikePath(inside2);
15804
+ }
15805
+ function replaceAll(text2, what, by) {
15806
+ if (what === "") return text2;
15807
+ return text2.split(what).join(by);
15388
15808
  }
15389
15809
 
15390
15810
  // src/job-report.ts
@@ -15431,14 +15851,14 @@ var PLAN_HEAD = /^План:$/;
15431
15851
  var PLAN_ROW = /^· (.+)$/;
15432
15852
  var STYLE = /^Стиль: /;
15433
15853
  var LEFT_TAIL = /\.\s+(Осталось .+)$/;
15434
- function readJob(text) {
15435
- const lines = text.split("\n").map((row) => row.trimEnd());
15436
- const head = HEAD.exec(lines[0]?.trim() ?? "");
15437
- if (head === null) return null;
15854
+ function readJob(text2) {
15855
+ const lines = text2.split("\n").map((row) => row.trimEnd());
15856
+ const head2 = HEAD.exec(lines[0]?.trim() ?? "");
15857
+ if (head2 === null) return null;
15438
15858
  const job = empty();
15439
- job.id = head[1] ?? "";
15440
- job.what = (head[2] ?? "").trim();
15441
- let tail = null;
15859
+ job.id = head2[1] ?? "";
15860
+ job.what = (head2[2] ?? "").trim();
15861
+ let tail2 = null;
15442
15862
  for (const row of lines.slice(1)) {
15443
15863
  const line = row.trim();
15444
15864
  if (line === "") continue;
@@ -15446,17 +15866,17 @@ function readJob(text) {
15446
15866
  const complaint = COMPLAINT.exec(line);
15447
15867
  if (complaint !== null) {
15448
15868
  job.complaints.push(complaint[1] ?? "");
15449
- tail = null;
15869
+ tail2 = null;
15450
15870
  continue;
15451
15871
  }
15452
15872
  if (PLAN_HEAD.test(line)) {
15453
- tail = null;
15873
+ tail2 = null;
15454
15874
  continue;
15455
15875
  }
15456
15876
  const planRow = PLAN_ROW.exec(line);
15457
15877
  if (planRow !== null) {
15458
15878
  job.plan.push(planRow[1] ?? "");
15459
- tail = null;
15879
+ tail2 = null;
15460
15880
  continue;
15461
15881
  }
15462
15882
  const behind = BEHIND.exec(line);
@@ -15487,13 +15907,13 @@ function readJob(text) {
15487
15907
  if (done !== null) {
15488
15908
  job.status = "done";
15489
15909
  job.sec = seconds(done[1]);
15490
- tail = [];
15910
+ tail2 = [];
15491
15911
  continue;
15492
15912
  }
15493
15913
  const file2 = FILE.exec(line);
15494
15914
  if (file2 !== null && job.status === "done") {
15495
15915
  job.file = file2[1] ?? "";
15496
- tail = null;
15916
+ tail2 = null;
15497
15917
  continue;
15498
15918
  }
15499
15919
  const failed = FAILED.exec(line);
@@ -15501,20 +15921,20 @@ function readJob(text) {
15501
15921
  job.status = "error";
15502
15922
  job.sec = seconds(failed[1]);
15503
15923
  job.trouble = failed[2] ?? "";
15504
- tail = null;
15924
+ tail2 = null;
15505
15925
  continue;
15506
15926
  }
15507
15927
  if (ABORTED.test(line)) {
15508
15928
  job.status = "aborted";
15509
15929
  job.trouble = line;
15510
15930
  job.sec = seconds(ABORTED_SEC.exec(line)?.[1]);
15511
- tail = null;
15931
+ tail2 = null;
15512
15932
  continue;
15513
15933
  }
15514
15934
  const advice = ADVICE.exec(line);
15515
15935
  if (advice !== null) {
15516
15936
  job.advice = advice[1] ?? "";
15517
- tail = null;
15937
+ tail2 = null;
15518
15938
  continue;
15519
15939
  }
15520
15940
  if (WAITING.test(line)) {
@@ -15528,82 +15948,560 @@ function readJob(text) {
15528
15948
  job.sec = seconds(running[1]);
15529
15949
  continue;
15530
15950
  }
15531
- if (tail !== null) {
15532
- tail.push(line);
15533
- job.outcome = tail.join("\n");
15951
+ if (tail2 !== null) {
15952
+ tail2.push(line);
15953
+ job.outcome = tail2.join("\n");
15534
15954
  continue;
15535
15955
  }
15536
15956
  if (job.status === "running" || job.status === "waiting") job.message = line;
15537
15957
  }
15538
15958
  return job;
15539
15959
  }
15540
- function seconds(text) {
15541
- if (text === void 0) return 0;
15542
- const value = Number(text.replace(",", "."));
15960
+ function seconds(text2) {
15961
+ if (text2 === void 0) return 0;
15962
+ const value = Number(text2.replace(",", "."));
15543
15963
  return Number.isFinite(value) ? value : 0;
15544
15964
  }
15545
- var WIDGET_MIME = "text/html;profile=mcp-app";
15546
- function widgetStamp(html) {
15547
- return createHash("sha256").update(html).digest("hex").slice(0, 8);
15548
- }
15549
- var WIDGET_PLATE_CSS = `
15550
- :root { --plate: #ffffff; --plate-end: #f5f5f7; }
15551
- :root:has(> body.dark) { --plate: #1d1d1f; --plate-end: #000000; }
15552
- :root:has(> body.full) { min-height: 100%; }
15553
- html {
15554
- background: var(--plate-end) linear-gradient(var(--plate) calc(100% - 197px), var(--plate-end)) no-repeat;
15965
+
15966
+ // src/crash-report.ts
15967
+ var RESEND_AFTER_MS = 30 * 6e4;
15968
+ var QUEUE_CAP = 20;
15969
+ var FLUSH_PER_PASS = 5;
15970
+ var FLUSH_EVERY_MS = 6e4;
15971
+ var SEND_TIMEOUT_MS = 3e3;
15972
+ var JOB_LOG_TAIL = 64 * 1024;
15973
+ var PYTHON_TRACEBACK = "Traceback (most recent call last):";
15974
+ var CrashReporter = class {
15975
+ #deps;
15976
+ #now;
15977
+ #inflight = /* @__PURE__ */ new Set();
15978
+ #lastFlush = Number.NEGATIVE_INFINITY;
15979
+ #host = null;
15980
+ /**
15981
+ * Строки из аргументов недавних вызовов — только в памяти процесса.
15982
+ *
15983
+ * ⚠ ЗАЧЕМ. Задание упало — отчёт о нём приходит на `wait`, а в аргументах
15984
+ * `wait` лишь номер задания; путь к ролику был в аргументах `render`,
15985
+ * который задание запустил. Без этой памяти путь с пробелами из трейса ffmpeg
15986
+ * вычищался бы только общими правилами, а они его режут на куски (ревью
15987
+ * безопасности 2026-09-24). На диск не пишется: это ровно то, что мы
15988
+ * обещали не хранить.
15989
+ */
15990
+ #recentSecrets = [];
15991
+ #ffmpeg = null;
15992
+ constructor(deps) {
15993
+ this.#deps = deps;
15994
+ this.#now = deps.now ?? (() => Date.now());
15555
15995
  }
15556
- `;
15557
- var WIDGET_HEADING_CSS = `
15558
- :root {
15559
- --font-display: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Icons", "Helvetica Neue", Helvetica, Arial, sans-serif;
15996
+ get canSend() {
15997
+ return this.#deps.canSend;
15560
15998
  }
15561
- h2, .end__t { font-family: var(--font-display); }
15562
- `;
15563
-
15564
- // src/runbar.ts
15565
- var RUNBAR_RESOURCE_URI = `ui://naparnik/runbar-${widgetStamp(runbarWidgetHtml())}`;
15566
- var RUNBAR_MIME = WIDGET_MIME;
15567
- function runbarResource() {
15568
- return {
15569
- resource: {
15570
- uri: RUNBAR_RESOURCE_URI,
15571
- name: "\u0425\u043E\u0434 \u0440\u0430\u0431\u043E\u0442\u044B",
15572
- description: "\u041F\u043E\u043B\u043E\u0441\u0430 \u044D\u0442\u0430\u043F\u043E\u0432: \u0447\u0442\u043E \u0434\u0435\u043B\u0430\u0435\u0442\u0441\u044F \u0441\u0435\u0439\u0447\u0430\u0441 \u0438 \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0443\u0436\u0435 \u0438\u0434\u0451\u0442",
15573
- mimeType: RUNBAR_MIME
15574
- },
15575
- contents: [{
15576
- uri: RUNBAR_RESOURCE_URI,
15577
- mimeType: RUNBAR_MIME,
15578
- text: runbarWidgetHtml(),
15579
- _meta: { ui: { prefersBorder: true } }
15580
- }]
15581
- };
15582
- }
15583
- function runbarWidgetHtml() {
15584
- return `<!DOCTYPE html>
15585
- <html lang="ru">
15586
- <head>
15587
- <meta charset="utf-8">
15588
- <style>
15589
- /* \u2500\u2500\u2500 \u041C\u0438\u0440 \u043F\u043E\u043B\u043E\u0441\u044B \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
15590
- \u0426\u0432\u0435\u0442\u0430 \u0442\u0435 \u0436\u0435, \u0447\u0442\u043E \u0443 \u0444\u043E\u0440\u043C\u044B \u0432\u044B\u0431\u043E\u0440\u0430 \u0438 \u043D\u0430 \u043B\u0435\u043D\u0434\u0438\u043D\u0433\u0435: \u0436\u0451\u043B\u0442\u044B\u0439 \u0432\u0435\u0434\u0451\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u2014
15591
- \u0437\u0434\u0435\u0441\u044C \u044D\u0442\u043E \u0438\u0434\u0443\u0449\u0438\u0439 \u044D\u0442\u0430\u043F, \u0433\u043E\u043B\u0443\u0431\u043E\u0439 \u0432\u0435\u0434\u0451\u0442 \u0433\u043E\u043B\u043E\u0441 \u2014 \u0441\u0442\u0440\u043E\u043A\u0443 \u043E \u0442\u043E\u043C, \u0447\u0442\u043E \u043F\u0440\u043E\u0438\u0441\u0445\u043E\u0434\u0438\u0442.
15592
-
15593
- \u26A0 \u0421\u0412\u0415\u0422 \u2014 \u041E\u0421\u041D\u041E\u0412\u0410, \u0422\u0401\u041C\u041D\u0410\u042F \u0422\u0415\u041C\u0410 \u0422\u041E\u041B\u042C\u041A\u041E \u0414\u041E\u041F\u0418\u0421\u042B\u0412\u0410\u0415\u0422. \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430: \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435
15594
- \u0443 \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u0430 \u043E\u0434\u043D\u043E, \u0438 \u043F\u0440\u0430\u0432\u043A\u0430 \u0441\u0432\u0435\u0442\u043B\u043E\u0439 \u0442\u0435\u043C\u044B \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u043C\u043E\u043B\u0447\u0430 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0442\u0451\u043C\u043D\u0443\u044E.
15595
-
15596
- \u26A0 \u041E \xABcolor-scheme\xBB: \u0435\u0433\u043E \u0437\u0434\u0435\u0441\u044C \u041D\u0415\u0422, \u0438 \u044D\u0442\u043E \u043D\u0435 \u0437\u0430\u0431\u044B\u0442\u043E. \u0421\u043E \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435\u043C
15597
- \xABlight dark\xBB \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0442 \u043A\u043E\u043C\u043F\u043E\u0437\u0438\u0442\u0438\u0442\u044C iframe \u0441 \u043F\u043E\u0432\u0435\u0440\u0445\u043D\u043E\u0441\u0442\u044C\u044E \u0445\u043E\u0441\u0442\u0430 \u0438
15598
- \u0437\u0430\u043B\u0438\u0432\u0430\u0435\u0442 \u0435\u0433\u043E \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u043C \u0445\u043E\u043B\u0441\u0442\u043E\u043C \u043F\u043E \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0435 \u0421\u0418\u0421\u0422\u0415\u041C\u042B: \u043F\u0440\u0438 \u0441\u0432\u0435\u0442\u043B\u043E\u043C \u041A\u043B\u043E\u0434\u0435
15599
- \u043D\u0430 \u0442\u0451\u043C\u043D\u043E\u0439 macOS \u043F\u043E\u043B\u043E\u0441\u0430 \u043F\u043E\u043B\u0443\u0447\u0430\u043B\u0430 \u0431\u044B \u0447\u0451\u0440\u043D\u044B\u0439 \u0445\u043E\u043B\u0441\u0442 \u043F\u043E\u0434 \u0442\u0451\u043C\u043D\u044B\u043C\u0438 \u0447\u0435\u0440\u043D\u0438\u043B\u0430\u043C\u0438.
15600
- \u041E\u0448\u0438\u0431\u043A\u0430 \u043C\u043E\u043B\u0447\u0430\u043B\u0438\u0432\u0430\u044F, \u0432 \u043A\u043E\u043D\u0441\u043E\u043B\u0438 \u043F\u0443\u0441\u0442\u043E. \u041F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E \u043D\u0430 \u0444\u043E\u0440\u043C\u0435 \u0432\u044B\u0431\u043E\u0440\u0430 2026-09-10. */
15601
- /* \u041F\u043E\u0434\u043B\u043E\u0436\u043A\u0430 \u043E\u0431\u0449\u0430\u044F \u0441 \u0444\u043E\u0440\u043C\u043E\u0439 \u0432\u044B\u0431\u043E\u0440\u0430 \u0438 \u043B\u0435\u0436\u0438\u0442 \u0432 widget.ts. */
15602
- ${WIDGET_PLATE_CSS}
15603
- ${WIDGET_HEADING_CSS}
15604
- :root {
15605
- --ink: #1d1d1f;
15606
- --ink-soft: #5E5547;
15999
+ get home() {
16000
+ return this.#deps.home;
16001
+ }
16002
+ /** Запомнить программу-хост: при падении всего процесса контекста вызова нет. */
16003
+ /** Запомнить строки аргументов вызова — чтобы вычеркнуть их из будущих отчётов. */
16004
+ noteSecrets(secrets) {
16005
+ const fresh = secrets.filter((one) => one.length >= 4);
16006
+ if (fresh.length === 0) return;
16007
+ this.#recentSecrets = [...this.#recentSecrets.filter((one) => !fresh.includes(one)), ...fresh].slice(-50);
16008
+ }
16009
+ noteHost(host) {
16010
+ if (host !== null) this.#host = host;
16011
+ }
16012
+ /** Отправить отчёт мимо ответа. Для тестов и смоука — `settled()`. */
16013
+ submit(crash) {
16014
+ this.#track(this.#deliver(crash));
16015
+ }
16016
+ /**
16017
+ * Процесс падает: отчёт кладётся на диск СИНХРОННО и уходит на следующем
16018
+ * запуске. Сеть здесь не трогаем — процессу осталось жить миллисекунды, и
16019
+ * недоставленный наполовину запрос хуже файла, который точно дойдёт.
16020
+ */
16021
+ persist(crash) {
16022
+ try {
16023
+ const report = this.#build(crash);
16024
+ if (report !== null) this.#enqueue(report);
16025
+ } catch {
16026
+ }
16027
+ }
16028
+ /** Досылка очереди — не чаще раза в минуту. */
16029
+ flushSoon() {
16030
+ const now = this.#now();
16031
+ if (now - this.#lastFlush < FLUSH_EVERY_MS) return;
16032
+ this.#lastFlush = now;
16033
+ this.#track(this.#flush());
16034
+ }
16035
+ /** Дождаться всего, что ушло мимо ответа. Боевому пути не нужно. */
16036
+ async settled() {
16037
+ while (this.#inflight.size > 0) await Promise.allSettled([...this.#inflight]);
16038
+ }
16039
+ /** Задание уже разобрано: отчёт о нём ушёл раньше, в этом или прошлом запуске. */
16040
+ jobSeen(id) {
16041
+ return this.#state().jobs.includes(id);
16042
+ }
16043
+ rememberJob(id) {
16044
+ const state = this.#state();
16045
+ state.jobs = [...state.jobs.filter((one) => one !== id), id].slice(-50);
16046
+ this.#save(state);
16047
+ }
16048
+ scrubContext(secrets) {
16049
+ return {
16050
+ home: this.#deps.osHome ?? homedir(),
16051
+ user: this.#deps.osUser ?? safeUser(),
16052
+ naparnikHome: this.#deps.home,
16053
+ shellDir: this.#deps.shellDir ?? ownDir(),
16054
+ secrets
16055
+ };
16056
+ }
16057
+ #track(work) {
16058
+ const guarded = work.catch(() => void 0);
16059
+ this.#inflight.add(guarded);
16060
+ void guarded.finally(() => this.#inflight.delete(guarded));
16061
+ }
16062
+ async #deliver(crash) {
16063
+ const report = this.#build(crash);
16064
+ if (report === null) return;
16065
+ report.versions.ffmpeg = await this.#ffmpegVersion();
16066
+ const verdict = await this.#send(report);
16067
+ if (verdict === "retry") this.#enqueue(report);
16068
+ }
16069
+ async #flush() {
16070
+ const dir = this.#queueDir();
16071
+ let files;
16072
+ try {
16073
+ files = readdirSync(dir).filter((f) => f.endsWith(".json")).sort();
16074
+ } catch {
16075
+ return;
16076
+ }
16077
+ for (const file2 of files.slice(0, FLUSH_PER_PASS)) {
16078
+ const path2 = join(dir, file2);
16079
+ let report;
16080
+ try {
16081
+ report = JSON.parse(readFileSync(path2, "utf8"));
16082
+ } catch {
16083
+ rmSync(path2, { force: true });
16084
+ continue;
16085
+ }
16086
+ const verdict = await this.#send(report);
16087
+ if (verdict === "retry") return;
16088
+ rmSync(path2, { force: true });
16089
+ }
16090
+ }
16091
+ /**
16092
+ * Отправить и решить, что делать с отчётом дальше.
16093
+ *
16094
+ * ⚠ В ОЧЕРЕДЬ ИДЁТ ТОЛЬКО ТО, ЧТО ПОВТОР ИСПРАВИТ: нет сети, таймаут, 5xx,
16095
+ * 429 и «ключа нет» (человек впишет его завтра). 400 и 413 — отчёт кривой,
16096
+ * 401 и 403 — ключ не наш: повтор даст тот же отказ, а очередь с потолком
16097
+ * вытесняла бы им живые отчёты.
16098
+ */
16099
+ async #send(report) {
16100
+ if (!CrashReportRequest.safeParse(report).success) return "drop";
16101
+ try {
16102
+ await this.#deps.client.postMontage(CRASH_REPORT_PATH, report, {
16103
+ once: true,
16104
+ timeoutMs: this.#deps.timeoutMs ?? SEND_TIMEOUT_MS
16105
+ });
16106
+ return "sent";
16107
+ } catch (trouble) {
16108
+ if (trouble instanceof NaparnikAuthError || trouble instanceof NaparnikScopeError) return "drop";
16109
+ if (trouble instanceof NaparnikClientError) return trouble.status === 429 ? "retry" : "drop";
16110
+ return "retry";
16111
+ }
16112
+ }
16113
+ /**
16114
+ * Собрать отчёт: обезличить, обрезать, посчитать отпечаток и решить по
16115
+ * частоте. `null` — этот отпечаток уже уходил меньше получаса назад, и
16116
+ * повтор записан в счётчик.
16117
+ */
16118
+ #build(crash) {
16119
+ const fingerprint = fingerprintOf2(crash.errorType, crash.frames);
16120
+ const state = this.#state();
16121
+ const now = this.#now();
16122
+ const seen = state.fingerprints[fingerprint];
16123
+ if (seen !== void 0 && now - seen.sentAt < RESEND_AFTER_MS) {
16124
+ seen.pending += 1;
16125
+ this.#save(state);
16126
+ return null;
16127
+ }
16128
+ const repeats = Math.min(seen?.pending ?? 0, CRASH_LIMITS.repeats);
16129
+ state.fingerprints[fingerprint] = { sentAt: now, pending: 0 };
16130
+ prune(state, now);
16131
+ this.#save(state);
16132
+ const context = this.scrubContext([...crash.secrets, ...this.#recentSecrets]);
16133
+ const clean = (text2) => scrub(text2, context);
16134
+ return {
16135
+ contract: CRASH_CONTRACT,
16136
+ occurred_at: new Date(now).toISOString(),
16137
+ where: crash.where,
16138
+ tool: crash.tool !== null && CRASH_TOOL_NAME.test(crash.tool) ? crash.tool : null,
16139
+ // ⚠ РЕЖЕМ ДО ОБЕЗЛИЧИВАНИЯ С ЗАПАСОМ И ЕЩЁ РАЗ ПОСЛЕ. Обезличивание идёт
16140
+ // синхронно, до ответа инструмента, а stderr движка бывает мегабайтами:
16141
+ // без первой резки длинный текст подвешивал бы ответ на секунды (ревью
16142
+ // безопасности 2026-09-24). Запас — вдвое: метки короче того, что заменяют.
16143
+ stage: crash.stage === null ? null : head(clean(head(crash.stage, 2 * CRASH_LIMITS.stage)), CRASH_LIMITS.stage),
16144
+ error_type: head(clean(head(crash.errorType, 2 * CRASH_LIMITS.errorType)), CRASH_LIMITS.errorType) || "Error",
16145
+ message: head(clean(head(crash.message, 2 * CRASH_LIMITS.message)), CRASH_LIMITS.message),
16146
+ trace: crash.keep === "tail" ? tail(clean(crash.trace.slice(-2 * CRASH_LIMITS.trace)), CRASH_LIMITS.trace) : head(clean(head(crash.trace, 2 * CRASH_LIMITS.trace)), CRASH_LIMITS.trace),
16147
+ fingerprint,
16148
+ repeats,
16149
+ os: {
16150
+ platform: head(platform(), CRASH_LIMITS.osPlatform),
16151
+ release: head(release(), CRASH_LIMITS.osRelease),
16152
+ arch: head(arch(), CRASH_LIMITS.osArch)
16153
+ },
16154
+ host: hostOf(crash.host ?? this.#host),
16155
+ versions: {
16156
+ shell: head(VERSION, CRASH_LIMITS.shellVersion),
16157
+ engine: orNull(this.#deps.engineVersion(), CRASH_LIMITS.engineVersion),
16158
+ python: orNull(crash.python, CRASH_LIMITS.pythonVersion),
16159
+ ffmpeg: null
16160
+ },
16161
+ recent_calls: recentCalls(this.#deps.home)
16162
+ };
16163
+ }
16164
+ #enqueue(report) {
16165
+ try {
16166
+ const dir = this.#queueDir();
16167
+ mkdirSync(dir, { recursive: true });
16168
+ const name = `${String(this.#now()).padStart(15, "0")}-${report.fingerprint}.json`;
16169
+ const temporary = join(dir, `${name}.tmp`);
16170
+ writeFileSync(temporary, JSON.stringify(report), { mode: 384 });
16171
+ renameSync(temporary, join(dir, name));
16172
+ const files = readdirSync(dir).filter((f) => f.endsWith(".json")).sort();
16173
+ for (const old of files.slice(0, Math.max(0, files.length - QUEUE_CAP))) {
16174
+ rmSync(join(dir, old), { force: true });
16175
+ }
16176
+ } catch {
16177
+ }
16178
+ }
16179
+ #queueDir() {
16180
+ return join(this.#deps.home, "crash-reports", "queue");
16181
+ }
16182
+ #stateFile() {
16183
+ return join(this.#deps.home, "crash-reports", "seen.json");
16184
+ }
16185
+ #state() {
16186
+ try {
16187
+ const parsed = JSON.parse(readFileSync(this.#stateFile(), "utf8"));
16188
+ return {
16189
+ fingerprints: typeof parsed.fingerprints === "object" && parsed.fingerprints !== null ? parsed.fingerprints : {},
16190
+ jobs: Array.isArray(parsed.jobs) ? parsed.jobs.filter((j) => typeof j === "string") : []
16191
+ };
16192
+ } catch {
16193
+ return { fingerprints: {}, jobs: [] };
16194
+ }
16195
+ }
16196
+ #save(state) {
16197
+ try {
16198
+ mkdirSync(dirname(this.#stateFile()), { recursive: true });
16199
+ writeFileSync(this.#stateFile(), JSON.stringify(state), { mode: 384 });
16200
+ } catch {
16201
+ }
16202
+ }
16203
+ #ffmpegVersion() {
16204
+ this.#ffmpeg ??= (this.#deps.ffmpegVersion ?? (() => probeFfmpeg(this.#deps.home)))().catch(() => null);
16205
+ return this.#ffmpeg.then((v) => orNull(v, CRASH_LIMITS.ffmpegVersion));
16206
+ }
16207
+ };
16208
+ function prune(state, now) {
16209
+ for (const [key, value] of Object.entries(state.fingerprints)) {
16210
+ if (now - value.sentAt > 24 * 36e5 && value.pending === 0) delete state.fingerprints[key];
16211
+ }
16212
+ }
16213
+ function withCrashReport(crashes) {
16214
+ return (tool) => {
16215
+ if (crashes === null) return tool;
16216
+ const original = tool.execute.bind(tool);
16217
+ return {
16218
+ ...tool,
16219
+ async execute(input, ...rest) {
16220
+ crashes.flushSoon();
16221
+ const host = hostFromContext(rest[0]);
16222
+ crashes.noteHost(host);
16223
+ crashes.noteSecrets(secretsOf(input));
16224
+ let result;
16225
+ try {
16226
+ result = await original(input, ...rest);
16227
+ } catch (trouble) {
16228
+ const crash = fromThrown(trouble, "tool", tool.name, input, host);
16229
+ crashes.submit(crash);
16230
+ return toolError(brokenWords(crash, crashes));
16231
+ }
16232
+ return caught(result, tool.name, input, host, crashes);
16233
+ }
16234
+ };
16235
+ };
16236
+ }
16237
+ function caught(result, name, input, host, crashes) {
16238
+ if (result.type === "error") {
16239
+ const parsed2 = pythonCrash(result.error);
16240
+ if (parsed2 === null) return result;
16241
+ const crash2 = { ...parsed2, where: "tool", tool: name, stage: null, secrets: secretsOf(input), host };
16242
+ crashes.submit(crash2);
16243
+ return toolError(brokenWords(crash2, crashes));
16244
+ }
16245
+ const job = result.structured;
16246
+ if (job === void 0 || job["contract"] !== JOB_CONTRACT || job["status"] !== "error") return result;
16247
+ const id = typeof job["id"] === "string" ? job["id"] : "";
16248
+ if (!/^[\w.-]{1,80}$/.test(id) || crashes.jobSeen(id)) return result;
16249
+ const parsed = pythonCrash(jobLogTail(crashes.home, id));
16250
+ if (parsed === null) return result;
16251
+ crashes.rememberJob(id);
16252
+ const stage = typeof job["now"] === "string" && job["now"] !== "" ? job["now"] : null;
16253
+ const crash = { ...parsed, where: "job", tool: name, stage, secrets: secretsOf(input), host };
16254
+ crashes.submit(crash);
16255
+ const notice = `
16256
+
16257
+ ${brokenWords(crash, crashes)}`;
16258
+ return typeof result.content === "string" ? { ...result, content: result.content + notice } : { ...result, content: [...result.content, { type: "text", text: notice.trim() }] };
16259
+ }
16260
+ function brokenWords(crash, crashes) {
16261
+ const essence = head(scrub(head(crash.essence, 600), crashes.scrubContext(crash.secrets)), 300);
16262
+ return [
16263
+ "\u0421\u043B\u043E\u043C\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u043D\u0430\u0448\u0435\u0439 \u0441\u0442\u043E\u0440\u043E\u043D\u0435, \u043D\u0435 \u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430.",
16264
+ crashes.canSend ? `\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u0441\u043A\u0430\u0436\u0438, \u0447\u0442\u043E \u0442\u0435\u0445\u043D\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u043E\u0442\u0447\u0451\u0442 \u0443\u0445\u043E\u0434\u0438\u0442 \u043D\u0430\u043C \u0441\u0430\u043C \u0438 \u0440\u0430\u0441\u0441\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0443\u0436\u043D\u043E, \u0438 \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u0439 \u0435\u0449\u0451 \u0440\u0430\u0437. \u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0441\u044F \u2014 \u043F\u0443\u0441\u0442\u044C \u043D\u0430\u043F\u0438\u0448\u0435\u0442 \u043D\u0430\u043C \u0432 \u0431\u043E\u0442 ${KEY_SOURCE_BOT}.` : `\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u0439 \u0435\u0449\u0451 \u0440\u0430\u0437. \u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0441\u044F \u2014 \u043F\u0443\u0441\u0442\u044C \u043D\u0430\u043F\u0438\u0448\u0435\u0442 \u043D\u0430\u043C \u0432 \u0431\u043E\u0442 ${KEY_SOURCE_BOT}.`,
16265
+ `\u0421\u0443\u0442\u044C \u0434\u043B\u044F \u0442\u0435\u0431\u044F, \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u043D\u0435 \u0437\u0430\u0447\u0438\u0442\u044B\u0432\u0430\u0439: ${essence}`
16266
+ ].join("\n");
16267
+ }
16268
+ function watchProcessCrashes(crashes, proc = process, exit = (code) => process.exit(code)) {
16269
+ const fall = (trouble) => {
16270
+ crashes.persist(fromThrown(trouble, "process", null, {}, null));
16271
+ try {
16272
+ process.stderr.write(`naparnik-mcp: \u0443\u043F\u0430\u043B \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u2014 ${trouble instanceof Error ? trouble.stack ?? trouble.message : String(trouble)}
16273
+ `);
16274
+ } catch {
16275
+ }
16276
+ exit(1);
16277
+ };
16278
+ proc.on("uncaughtException", fall);
16279
+ proc.on("unhandledRejection", fall);
16280
+ crashes.flushSoon();
16281
+ }
16282
+ function fromThrown(trouble, where, tool, input, host) {
16283
+ const error40 = trouble instanceof Error ? trouble : new Error(String(trouble));
16284
+ const errorType = error40.name || error40.constructor.name || "Error";
16285
+ const trace = error40.stack ?? `${errorType}: ${error40.message}`;
16286
+ const frames = jsFrames(trace);
16287
+ return {
16288
+ where,
16289
+ tool,
16290
+ stage: null,
16291
+ errorType,
16292
+ message: error40.message,
16293
+ trace,
16294
+ keep: "head",
16295
+ frames: frames.slice(0, 3).map((f) => `${f.file}:${f.func}`),
16296
+ python: null,
16297
+ secrets: secretsOf(input),
16298
+ host,
16299
+ essence: `${errorType}: ${error40.message}${frames[0] ? ` \u2014 ${frames[0].file}` : ""}`
16300
+ };
16301
+ }
16302
+ function jsFrames(stack) {
16303
+ const frames = [];
16304
+ for (const line of stack.split("\n")) {
16305
+ const m = /^\s*at (?:(.+?) \()?(.+?):\d+:\d+\)?\s*$/.exec(line);
16306
+ if (m === null) continue;
16307
+ const file2 = basename(m[2] ?? "").replace(/-[A-Za-z0-9]{6,}(\.[cm]?js)$/, "$1");
16308
+ frames.push({ file: file2, func: m[1] ?? "<anonymous>" });
16309
+ }
16310
+ return frames;
16311
+ }
16312
+ function pythonCrash(text2) {
16313
+ const start = text2.lastIndexOf(PYTHON_TRACEBACK);
16314
+ if (start < 0) return null;
16315
+ const block = text2.slice(start).replace(/\s+$/, "");
16316
+ const lines = block.split("\n");
16317
+ const frames = [];
16318
+ let lastFrame = -1;
16319
+ lines.forEach((line, at2) => {
16320
+ const m2 = /^\s*File "(.+)", line (\d+), in (.+)$/.exec(line);
16321
+ if (m2 === null) return;
16322
+ frames.push({ file: m2[1] ?? "", line: m2[2] ?? "", func: (m2[3] ?? "").trim() });
16323
+ lastFrame = at2;
16324
+ });
16325
+ if (frames.length === 0) return null;
16326
+ if (frames.some((f) => /[\\/]mine[\\/]steps[\\/]/.test(f.file))) return null;
16327
+ const at = lines.findIndex((line, n) => n > lastFrame && line !== "" && !/^\s/.test(line));
16328
+ if (at < 0) return null;
16329
+ const m = /^([A-Za-z_][\w.]*)(?::\s?(.*))?$/.exec(lines[at] ?? "");
16330
+ if (m === null) return null;
16331
+ const errorType = m[1] ?? "Error";
16332
+ if (/(?:^|\.)(?:KeyboardInterrupt|SystemExit|Explained|Refusal)$/.test(errorType)) return null;
16333
+ const message = [m[2] ?? "", ...lines.slice(at + 1)].join("\n").trim();
16334
+ const innermost = frames[frames.length - 1];
16335
+ const firstLine = message.split("\n")[0] ?? "";
16336
+ return {
16337
+ errorType,
16338
+ message,
16339
+ trace: block,
16340
+ keep: "tail",
16341
+ frames: frames.slice(-3).map((f) => `${basename(f.file.replace(/\\/g, "/"))}:${f.func}`),
16342
+ python: pythonVersionIn(frames.map((f) => f.file)),
16343
+ essence: `${errorType}${firstLine === "" ? "" : `: ${firstLine}`}` + (innermost ? ` \u2014 ${basename(innermost.file.replace(/\\/g, "/"))}:${innermost.line}` : "")
16344
+ };
16345
+ }
16346
+ function pythonVersionIn(files) {
16347
+ for (const file2 of files) {
16348
+ const m = /python(\d)\.?(\d{1,2})[\\/]/i.exec(file2);
16349
+ if (m !== null) return `${m[1]}.${m[2]}`;
16350
+ }
16351
+ return null;
16352
+ }
16353
+ function jobLogTail(home, id) {
16354
+ const file2 = join(home, "logs", `${id}.log`);
16355
+ let fd = null;
16356
+ try {
16357
+ fd = openSync(file2, "r");
16358
+ const size = fstatSync(fd).size;
16359
+ const length = Math.min(size, JOB_LOG_TAIL);
16360
+ const buffer = Buffer.alloc(length);
16361
+ readSync(fd, buffer, 0, length, size - length);
16362
+ return buffer.toString("utf8");
16363
+ } catch {
16364
+ return "";
16365
+ } finally {
16366
+ if (fd !== null) try {
16367
+ closeSync(fd);
16368
+ } catch {
16369
+ }
16370
+ }
16371
+ }
16372
+ function fingerprintOf2(errorType, frames) {
16373
+ return createHash("sha256").update([errorType, ...frames].join("|")).digest("hex").slice(0, 16);
16374
+ }
16375
+ function secretsOf(input, depth = 0, out = []) {
16376
+ if (out.length >= 50 || depth > 4) return out;
16377
+ if (typeof input === "string") out.push(input);
16378
+ else if (Array.isArray(input)) for (const one of input) secretsOf(one, depth + 1, out);
16379
+ else if (typeof input === "object" && input !== null) {
16380
+ for (const one of Object.values(input)) secretsOf(one, depth + 1, out);
16381
+ }
16382
+ return out;
16383
+ }
16384
+ function recentCalls(home) {
16385
+ try {
16386
+ const text2 = readFileSync(join(home, CALLS_FILE), "utf8").slice(-8192);
16387
+ const names = [];
16388
+ for (const line of text2.split("\n")) {
16389
+ try {
16390
+ const tool = JSON.parse(line).tool;
16391
+ if (typeof tool === "string" && CRASH_TOOL_NAME.test(tool)) names.push(tool);
16392
+ } catch {
16393
+ }
16394
+ }
16395
+ return names.slice(-CRASH_LIMITS.recentCalls);
16396
+ } catch {
16397
+ return [];
16398
+ }
16399
+ }
16400
+ function hostFromContext(context) {
16401
+ const host = context?.host;
16402
+ return host !== void 0 && typeof host.name === "string" && typeof host.version === "string" ? host : null;
16403
+ }
16404
+ function hostOf(host) {
16405
+ return host === null ? null : { name: head(host.name, CRASH_LIMITS.hostName), version: head(host.version, CRASH_LIMITS.hostVersion) };
16406
+ }
16407
+ function probeFfmpeg(home) {
16408
+ const own = join(home, "bin", platform() === "win32" ? "ffmpeg.exe" : "ffmpeg");
16409
+ const program = existsSync(own) ? own : "ffmpeg";
16410
+ return new Promise((done) => {
16411
+ execFile(program, ["-version"], { timeout: 3e3, encoding: "utf8" }, (trouble, stdout) => {
16412
+ if (trouble !== null) return done(null);
16413
+ const m = /^ffmpeg version (\S+)/.exec(stdout);
16414
+ done(m?.[1] ?? null);
16415
+ });
16416
+ });
16417
+ }
16418
+ function ownDir() {
16419
+ try {
16420
+ return dirname(fileURLToPath(import.meta.url));
16421
+ } catch {
16422
+ return "";
16423
+ }
16424
+ }
16425
+ function safeUser() {
16426
+ try {
16427
+ return userInfo().username;
16428
+ } catch {
16429
+ return "";
16430
+ }
16431
+ }
16432
+ function head(text2, max) {
16433
+ return text2.length <= max ? text2 : text2.slice(0, max);
16434
+ }
16435
+ function tail(text2, max) {
16436
+ if (text2.length <= max) return text2;
16437
+ const cut = "\u2026(\u043D\u0430\u0447\u0430\u043B\u043E \u043E\u0431\u0440\u0435\u0437\u0430\u043D\u043E)\n";
16438
+ return cut + text2.slice(text2.length - (max - cut.length));
16439
+ }
16440
+ function orNull(value, max) {
16441
+ return value === null || value === "" ? null : head(value, max);
16442
+ }
16443
+ var WIDGET_MIME = "text/html;profile=mcp-app";
16444
+ function widgetStamp(html) {
16445
+ return createHash("sha256").update(html).digest("hex").slice(0, 8);
16446
+ }
16447
+ var WIDGET_PLATE_CSS = `
16448
+ :root { --plate: #ffffff; --plate-end: #f5f5f7; }
16449
+ :root:has(> body.dark) { --plate: #1d1d1f; --plate-end: #000000; }
16450
+ :root:has(> body.full) { min-height: 100%; }
16451
+ html {
16452
+ background: var(--plate-end) linear-gradient(var(--plate) calc(100% - 197px), var(--plate-end)) no-repeat;
16453
+ }
16454
+ `;
16455
+ var WIDGET_HEADING_CSS = `
16456
+ :root {
16457
+ --font-display: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Icons", "Helvetica Neue", Helvetica, Arial, sans-serif;
16458
+ }
16459
+ h2, .end__t { font-family: var(--font-display); }
16460
+ `;
16461
+
16462
+ // src/runbar.ts
16463
+ var RUNBAR_RESOURCE_URI = `ui://naparnik/runbar-${widgetStamp(runbarWidgetHtml())}`;
16464
+ var RUNBAR_MIME = WIDGET_MIME;
16465
+ function runbarResource() {
16466
+ return {
16467
+ resource: {
16468
+ uri: RUNBAR_RESOURCE_URI,
16469
+ name: "\u0425\u043E\u0434 \u0440\u0430\u0431\u043E\u0442\u044B",
16470
+ description: "\u041F\u043E\u043B\u043E\u0441\u0430 \u044D\u0442\u0430\u043F\u043E\u0432: \u0447\u0442\u043E \u0434\u0435\u043B\u0430\u0435\u0442\u0441\u044F \u0441\u0435\u0439\u0447\u0430\u0441 \u0438 \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0443\u0436\u0435 \u0438\u0434\u0451\u0442",
16471
+ mimeType: RUNBAR_MIME
16472
+ },
16473
+ contents: [{
16474
+ uri: RUNBAR_RESOURCE_URI,
16475
+ mimeType: RUNBAR_MIME,
16476
+ text: runbarWidgetHtml(),
16477
+ _meta: { ui: { prefersBorder: true } }
16478
+ }]
16479
+ };
16480
+ }
16481
+ function runbarWidgetHtml() {
16482
+ return `<!DOCTYPE html>
16483
+ <html lang="ru">
16484
+ <head>
16485
+ <meta charset="utf-8">
16486
+ <style>
16487
+ /* \u2500\u2500\u2500 \u041C\u0438\u0440 \u043F\u043E\u043B\u043E\u0441\u044B \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
16488
+ \u0426\u0432\u0435\u0442\u0430 \u0442\u0435 \u0436\u0435, \u0447\u0442\u043E \u0443 \u0444\u043E\u0440\u043C\u044B \u0432\u044B\u0431\u043E\u0440\u0430 \u0438 \u043D\u0430 \u043B\u0435\u043D\u0434\u0438\u043D\u0433\u0435: \u0436\u0451\u043B\u0442\u044B\u0439 \u0432\u0435\u0434\u0451\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u2014
16489
+ \u0437\u0434\u0435\u0441\u044C \u044D\u0442\u043E \u0438\u0434\u0443\u0449\u0438\u0439 \u044D\u0442\u0430\u043F, \u0433\u043E\u043B\u0443\u0431\u043E\u0439 \u0432\u0435\u0434\u0451\u0442 \u0433\u043E\u043B\u043E\u0441 \u2014 \u0441\u0442\u0440\u043E\u043A\u0443 \u043E \u0442\u043E\u043C, \u0447\u0442\u043E \u043F\u0440\u043E\u0438\u0441\u0445\u043E\u0434\u0438\u0442.
16490
+
16491
+ \u26A0 \u0421\u0412\u0415\u0422 \u2014 \u041E\u0421\u041D\u041E\u0412\u0410, \u0422\u0401\u041C\u041D\u0410\u042F \u0422\u0415\u041C\u0410 \u0422\u041E\u041B\u042C\u041A\u041E \u0414\u041E\u041F\u0418\u0421\u042B\u0412\u0410\u0415\u0422. \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430: \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435
16492
+ \u0443 \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u0430 \u043E\u0434\u043D\u043E, \u0438 \u043F\u0440\u0430\u0432\u043A\u0430 \u0441\u0432\u0435\u0442\u043B\u043E\u0439 \u0442\u0435\u043C\u044B \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u043C\u043E\u043B\u0447\u0430 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0442\u0451\u043C\u043D\u0443\u044E.
16493
+
16494
+ \u26A0 \u041E \xABcolor-scheme\xBB: \u0435\u0433\u043E \u0437\u0434\u0435\u0441\u044C \u041D\u0415\u0422, \u0438 \u044D\u0442\u043E \u043D\u0435 \u0437\u0430\u0431\u044B\u0442\u043E. \u0421\u043E \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435\u043C
16495
+ \xABlight dark\xBB \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0442 \u043A\u043E\u043C\u043F\u043E\u0437\u0438\u0442\u0438\u0442\u044C iframe \u0441 \u043F\u043E\u0432\u0435\u0440\u0445\u043D\u043E\u0441\u0442\u044C\u044E \u0445\u043E\u0441\u0442\u0430 \u0438
16496
+ \u0437\u0430\u043B\u0438\u0432\u0430\u0435\u0442 \u0435\u0433\u043E \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u043C \u0445\u043E\u043B\u0441\u0442\u043E\u043C \u043F\u043E \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0435 \u0421\u0418\u0421\u0422\u0415\u041C\u042B: \u043F\u0440\u0438 \u0441\u0432\u0435\u0442\u043B\u043E\u043C \u041A\u043B\u043E\u0434\u0435
16497
+ \u043D\u0430 \u0442\u0451\u043C\u043D\u043E\u0439 macOS \u043F\u043E\u043B\u043E\u0441\u0430 \u043F\u043E\u043B\u0443\u0447\u0430\u043B\u0430 \u0431\u044B \u0447\u0451\u0440\u043D\u044B\u0439 \u0445\u043E\u043B\u0441\u0442 \u043F\u043E\u0434 \u0442\u0451\u043C\u043D\u044B\u043C\u0438 \u0447\u0435\u0440\u043D\u0438\u043B\u0430\u043C\u0438.
16498
+ \u041E\u0448\u0438\u0431\u043A\u0430 \u043C\u043E\u043B\u0447\u0430\u043B\u0438\u0432\u0430\u044F, \u0432 \u043A\u043E\u043D\u0441\u043E\u043B\u0438 \u043F\u0443\u0441\u0442\u043E. \u041F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E \u043D\u0430 \u0444\u043E\u0440\u043C\u0435 \u0432\u044B\u0431\u043E\u0440\u0430 2026-09-10. */
16499
+ /* \u041F\u043E\u0434\u043B\u043E\u0436\u043A\u0430 \u043E\u0431\u0449\u0430\u044F \u0441 \u0444\u043E\u0440\u043C\u043E\u0439 \u0432\u044B\u0431\u043E\u0440\u0430 \u0438 \u043B\u0435\u0436\u0438\u0442 \u0432 widget.ts. */
16500
+ ${WIDGET_PLATE_CSS}
16501
+ ${WIDGET_HEADING_CSS}
16502
+ :root {
16503
+ --ink: #1d1d1f;
16504
+ --ink-soft: #5E5547;
15607
16505
  --card: #F3EDDF;
15608
16506
  --card-lift: #FBF7EC;
15609
16507
  --edge: rgba(20, 17, 12, 0.10);
@@ -16233,121 +17131,6 @@ function runbarWidgetHtml() {
16233
17131
  </html>`;
16234
17132
  }
16235
17133
 
16236
- // src/progress.ts
16237
- var realPace = {
16238
- now: () => Date.now(),
16239
- rest: (ms) => new Promise((done) => {
16240
- setTimeout(done, ms);
16241
- })
16242
- };
16243
- var WAIT_LIMIT_SECONDS = 55;
16244
- var POLL_EVERY_MS = 2e3;
16245
- var JOB_STILL_COMPUTING = "\u0415\u0449\u0451 \u0441\u0447\u0438\u0442\u0430\u0435\u0442";
16246
- var JOB_IN_QUEUE = "\u0416\u0434\u0451\u0442 \u043E\u0447\u0435\u0440\u0435\u0434\u0438";
16247
- function stillWorking(text) {
16248
- return text.includes(JOB_STILL_COMPUTING) || text.includes(JOB_IN_QUEUE);
16249
- }
16250
- function waitSeconds(asked) {
16251
- if (asked === void 0 || !Number.isFinite(asked) || asked <= 0) return 0;
16252
- return Math.min(Math.round(asked), WAIT_LIMIT_SECONDS);
16253
- }
16254
- function freshLines(text, told) {
16255
- const news = [];
16256
- for (const row of text.split("\n").slice(1)) {
16257
- const line = row.trim();
16258
- if (line.length === 0) continue;
16259
- if (line.startsWith(JOB_STILL_COMPUTING)) continue;
16260
- if (told.has(line)) continue;
16261
- told.add(line);
16262
- news.push(line);
16263
- }
16264
- return news;
16265
- }
16266
- async function followJob(opts) {
16267
- const pace = opts.pace ?? realPace;
16268
- const until = pace.now() + opts.seconds * 1e3;
16269
- const told = /* @__PURE__ */ new Set();
16270
- let state = await opts.ask();
16271
- retell(state, told, opts.tell);
16272
- while (state.ok && stillWorking(state.text) && pace.now() < until) {
16273
- await pace.rest(POLL_EVERY_MS);
16274
- try {
16275
- state = await opts.ask();
16276
- } catch {
16277
- continue;
16278
- }
16279
- retell(state, told, opts.tell);
16280
- }
16281
- return state;
16282
- }
16283
- function retell(state, told, tell) {
16284
- if (tell === void 0) return;
16285
- for (const line of freshLines(state.text, told)) tell(line);
16286
- }
16287
- var STAGE_WORDS = {
16288
- silence: "\u0443\u0431\u0435\u0440\u0443 \u043F\u0430\u0443\u0437\u044B",
16289
- voice: "\u0432\u044B\u0440\u043E\u0432\u043D\u044F\u044E \u0433\u043E\u043B\u043E\u0441",
16290
- speech: "\u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u044E \u0440\u0435\u0447\u044C",
16291
- phrases: "\u0440\u0430\u0437\u043B\u043E\u0436\u0443 \u0440\u0435\u0447\u044C \u043D\u0430 \u0444\u0440\u0430\u0437\u044B",
16292
- plan: "\u043F\u0440\u0438\u0434\u0443\u043C\u0430\u044E, \u0447\u0442\u043E \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0438 \u043A\u043E\u0433\u0434\u0430",
16293
- world: "\u043D\u0430\u0440\u0438\u0441\u0443\u044E \u0444\u043E\u043D \u0437\u0430 \u0433\u0435\u0440\u043E\u0435\u043C",
16294
- cutout: "\u043E\u0442\u0434\u0435\u043B\u044E \u0433\u0435\u0440\u043E\u044F \u043E\u0442 \u0444\u043E\u043D\u0430",
16295
- "hero-on-world": "\u043F\u043E\u0441\u0442\u0430\u0432\u043B\u044E \u0433\u0435\u0440\u043E\u044F \u043D\u0430 \u0444\u043E\u043D",
16296
- shots: "\u0440\u0430\u0441\u0441\u0442\u0430\u0432\u043B\u044E \u043A\u0440\u0443\u043F\u043D\u043E\u0441\u0442\u0438 \u043F\u043E \u043F\u043B\u0430\u043D\u0443",
16297
- screen: "\u0432\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u043F\u0438\u0441\u044C \u044D\u043A\u0440\u0430\u043D\u0430",
16298
- inserts: "\u043D\u0430\u0440\u0438\u0441\u0443\u044E \u0433\u0440\u0430\u0444\u0438\u043A\u0443 \u043F\u043E \u043F\u043B\u0430\u043D\u0443",
16299
- subtitles: "\u043D\u0430\u0440\u0438\u0441\u0443\u044E \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u044B",
16300
- layers: "\u043F\u043E\u043B\u043E\u0436\u0443 \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0438 \u043F\u043E\u0432\u0435\u0440\u0445",
16301
- accents: "\u0440\u0430\u0441\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0432\u0443\u043A\u043E\u0432\u044B\u0435 \u0430\u043A\u0446\u0435\u043D\u0442\u044B",
16302
- mix: "\u0441\u0432\u0435\u0434\u0443 \u0437\u0432\u0443\u043A",
16303
- intro: "\u043F\u043E\u0441\u0442\u0430\u0432\u043B\u044E \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0443 \u0432 \u043D\u0430\u0447\u0430\u043B\u043E"
16304
- };
16305
- var NEAR_STEPS = 3;
16306
- function montageLadder(stages) {
16307
- const words = stages.map((stage) => STAGE_WORDS[stage]).filter((word) => word !== void 0);
16308
- if (words.length === 0) return [];
16309
- const near = words.slice(0, NEAR_STEPS);
16310
- const far = words.slice(NEAR_STEPS);
16311
- const lines = [`\u0421\u043D\u0430\u0447\u0430\u043B\u0430 ${near.join(", \u043F\u043E\u0442\u043E\u043C ")}.`];
16312
- if (far.length > 0) {
16313
- lines.push(
16314
- `\u0414\u0430\u043B\u044C\u0448\u0435: ${far.join(", ")}. \u0428\u0430\u0433\u043E\u0432 \u0432\u0441\u0435\u0433\u043E \u0434\u043E ${stages.length} \u2014 \u0447\u0442\u043E \u043D\u0435 \u043F\u043E\u043D\u0430\u0434\u043E\u0431\u0438\u0442\u0441\u044F, \u043F\u0440\u043E\u043F\u0443\u0449\u0443.`
16315
- );
16316
- }
16317
- return lines;
16318
- }
16319
- var PAUSES_LADDER = [
16320
- "\u0423\u0431\u0435\u0440\u0443 \u043C\u043E\u043B\u0447\u0430\u043D\u0438\u0435 \u0438 \u0437\u0430\u043F\u0438\u043D\u043A\u0438 \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u044E.",
16321
- "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u044B, \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0438 \u043F\u043E\u0432\u0435\u0440\u0445 \u0438 \u0437\u0432\u0443\u043A \u2014 \u044D\u0442\u043E \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430, \u0435\u0451 \u0437\u0434\u0435\u0441\u044C \u043D\u0435\u0442."
16322
- ];
16323
- var PAYLOAD_LADDER = [
16324
- "\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u043F\u043E\u043B\u043E\u0436\u0443 \u0434\u0432\u0438\u0436\u043E\u043A \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u043E\u0434\u043D\u043E\u0439 \u043F\u0430\u043F\u043A\u043E\u0439 \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.",
16325
- "\u041F\u043E\u0442\u043E\u043C ffmpeg, python \u0438 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043C\u043E\u043D\u0442\u0430\u0436\u0430. \u0422\u044F\u0436\u0451\u043B\u043E\u0435 \u2014 \u0440\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, \u0447\u0438\u0441\u0442\u043A\u0430 \u0433\u043E\u043B\u043E\u0441\u0430 \u2014 \u0441\u0442\u0430\u0432\u0438\u0442\u0441\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E \u043F\u0440\u043E\u0441\u044C\u0431\u0435."
16326
- ];
16327
- var INSTALL_WORDS = {
16328
- ffmpeg: "\u043F\u043E\u0441\u0442\u0430\u0432\u043B\u044E ffmpeg \u2014 \u0438\u043C \u0441\u043E\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044F \u0432\u0438\u0434\u0435\u043E",
16329
- python: "\u043F\u043E\u0441\u0442\u0430\u0432\u043B\u044E python",
16330
- env: "\u0441\u043E\u0431\u0435\u0440\u0443 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043C\u043E\u043D\u0442\u0430\u0436\u0430",
16331
- model: "\u0441\u043A\u0430\u0447\u0430\u044E \u043C\u043E\u0434\u0435\u043B\u044C \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u044F \u0440\u0435\u0447\u0438 \u2014 \u043E\u043D\u0430 \u0438\u0437 \u0432\u0441\u0435\u0433\u043E \u0441\u0430\u043C\u0430\u044F \u0442\u044F\u0436\u0451\u043B\u0430\u044F",
16332
- cutout: "\u0441\u043A\u0430\u0447\u0430\u044E \u0442\u043E, \u0447\u0435\u043C \u043E\u0442\u0434\u0435\u043B\u044F\u044E \u0433\u0435\u0440\u043E\u044F \u043E\u0442 \u0444\u043E\u043D\u0430",
16333
- voice: "\u0441\u043A\u0430\u0447\u0430\u044E \u0442\u043E, \u0447\u0435\u043C \u0447\u0438\u0449\u0443 \u0433\u043E\u043B\u043E\u0441",
16334
- browser: "\u043F\u043E\u0441\u0442\u0430\u0432\u043B\u044E \u0431\u0440\u0430\u0443\u0437\u0435\u0440, \u043A\u043E\u0442\u043E\u0440\u044B\u043C \u0440\u0438\u0441\u0443\u044E \u0433\u0440\u0430\u0444\u0438\u043A\u0443"
16335
- };
16336
- function installLadder(step) {
16337
- if (step === "all") {
16338
- return [
16339
- "\u041F\u0440\u043E\u0432\u0435\u0440\u044E, \u0447\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442, \u0438 \u0434\u043E\u0441\u0442\u0430\u0432\u043B\u044E \u043F\u043E \u043E\u0447\u0435\u0440\u0435\u0434\u0438.",
16340
- "\u0422\u044F\u0436\u0451\u043B\u043E\u0435 \u0441\u044E\u0434\u0430 \u043D\u0435 \u0432\u0445\u043E\u0434\u0438\u0442: \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0447\u0438 \u0438 \u0432\u044B\u0440\u0435\u0437 \u0433\u0435\u0440\u043E\u044F \u0441\u0442\u0430\u0432\u044F\u0442\u0441\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \xAB\u0434\u0430\xBB \u043E\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430."
16341
- ];
16342
- }
16343
- const word = INSTALL_WORDS[step];
16344
- if (word === void 0) return [];
16345
- return [
16346
- `\u0421\u0435\u0439\u0447\u0430\u0441 ${word}.`,
16347
- "\u0418\u0434\u0451\u0442 \u043C\u0438\u043D\u0443\u0442\u0430\u043C\u0438. \u041E\u0431\u043E\u0440\u0432\u0451\u0442\u0441\u044F \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u0442\u043E\u0442 \u0436\u0435 \u0448\u0430\u0433: \u0443\u0436\u0435 \u0441\u043A\u0430\u0447\u0430\u043D\u043D\u043E\u0435 \u043D\u0435 \u043F\u0440\u043E\u043F\u0430\u0434\u0451\u0442."
16348
- ];
16349
- }
16350
-
16351
17134
  // src/tool-kit.ts
16352
17135
  function manifestUrl(engine, baseUrl) {
16353
17136
  return engine.variable("NAPARNIK_UPDATE_MANIFEST") ?? manifestUrlFor(baseUrl);
@@ -16363,31 +17146,6 @@ function aboutModels(passport) {
16363
17146
  function fromList(list, description) {
16364
17147
  return list !== void 0 && list.length > 0 ? external_exports.enum([...list]).describe(description) : external_exports.string().describe(description);
16365
17148
  }
16366
- function capabilitiesToSchema(passport, taken = /* @__PURE__ */ new Set()) {
16367
- const skills = passport?.["capabilities"];
16368
- if (skills === void 0) return {};
16369
- const schema = {};
16370
- for (const [name, o] of Object.entries(skills)) {
16371
- if (taken.has(name)) continue;
16372
- schema[name] = o["kind"] === "yes-no" ? external_exports.boolean().describe(o["about"]).optional() : external_exports.string().describe(o["about"]).optional();
16373
- }
16374
- return schema;
16375
- }
16376
- function readCapabilities(args, passport) {
16377
- const skills = passport?.["capabilities"];
16378
- if (skills === void 0) return [];
16379
- const line = [];
16380
- for (const [name, o] of Object.entries(skills)) {
16381
- const key = `--${name.split("_").join("-")}`;
16382
- if (o["kind"] === "yes-no") {
16383
- if (args[name] === true) line.push(key);
16384
- continue;
16385
- }
16386
- const value = args[name];
16387
- if (typeof value === "string") line.push(key, o["kind"] === "file" ? path(value) : value);
16388
- }
16389
- return line;
16390
- }
16391
17149
  function asToolResult(outcome) {
16392
17150
  return outcome.ok ? toolSuccess(outcome.text) : toolError(outcome.text);
16393
17151
  }
@@ -16409,7 +17167,6 @@ var KNOB_KIND = {
16409
17167
  "yes-no": "\u0434\u0430/\u043D\u0435\u0442"
16410
17168
  };
16411
17169
  var FROM_MINE = "mine";
16412
- var FROM_VENDOR = "vendor";
16413
17170
  function catalogInWords(data) {
16414
17171
  const lines = [];
16415
17172
  const version2 = data.engine?.version;
@@ -16468,12 +17225,10 @@ function catalogInWords(data) {
16468
17225
  const said = knobs.map((k) => `${k.name} (${k.kind !== void 0 ? KNOB_KIND[k.kind] ?? k.kind : "\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435"})`).join(", ");
16469
17226
  lines.push(`\u0420\u0443\u0447\u043A\u0438 \u043C\u043E\u043D\u0442\u0430\u0436\u0430, \u0438\u0445 \u043F\u0435\u0440\u0435\u0434\u0430\u044E\u0442 \u0432 render: ${said}.`);
16470
17227
  }
16471
- const skills = data.skills ?? [];
16472
- if (skills.length > 0) {
16473
- const said = skills.map((s) => s.origin === FROM_VENDOR ? `${s.name} \u2014 ${s.about ?? ""} (\u0447\u0435\u0440\u0435\u0437 ${s.read ?? ""})` : `${s.read ?? s.name} \u2014 ${s.about ?? ""}`).join("; ");
16474
- lines.push(
16475
- `\u0421\u043A\u0438\u043B\u043B\u044B \u043D\u0430 \u0434\u0438\u0441\u043A\u0435, \u0447\u0438\u0442\u0430\u0442\u044C plan \u0441 read, \u0440\u043E\u043B\u0438\u043A \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u043D\u0435 \u043D\u0443\u0436\u0435\u043D: ${said}. \u0413\u043E\u0434\u044F\u0442\u0441\u044F \u0438 \u043D\u0435 \u0434\u043B\u044F \u0440\u043E\u043B\u0438\u043A\u0430: \u043F\u0440\u0435\u0437\u0435\u043D\u0442\u0430\u0446\u0438\u044F, \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0430, \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430.`
16476
- );
17228
+ const index = data.handbook?.index;
17229
+ if (index !== void 0 && index.trim() !== "") {
17230
+ lines.push(`\u0421\u041F\u0420\u0410\u0412\u041E\u0427\u041D\u0418\u041A \u041C\u041E\u041D\u0422\u0410\u0416\u0401\u0420\u0410 \u2014 \u043E\u0433\u043B\u0430\u0432\u043B\u0435\u043D\u0438\u0435, \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0447\u0438\u0442\u0430\u0439 plan \u0441 read:
17231
+ ${index.trim()}`);
16477
17232
  }
16478
17233
  const style = data.style;
16479
17234
  if (style?.name !== void 0) {
@@ -16481,6 +17236,11 @@ function catalogInWords(data) {
16481
17236
  style.own === true ? `\u0421\u0442\u0438\u043B\u044C: \u0441\u0432\u043E\u0439 \u043F\u0430\u043A \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \xAB${style.name}\xBB \u2014 \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u043E\u0435 \u0437\u0430\u043F\u043E\u043C\u0438\u043D\u0430\u0435\u0442\u0441\u044F \u0432 \u043D\u0435\u0433\u043E.` : `\u0421\u0442\u0438\u043B\u044C: \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0439 \xAB${style.name}\xBB, \u0441\u0432\u043E\u0435\u0433\u043E \u043F\u0430\u043A\u0430 \u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0435\u0449\u0451 \u043D\u0435\u0442.`
16482
17237
  );
16483
17238
  }
17239
+ const mine = data.mine?.summary ?? [];
17240
+ if (mine.length > 0) {
17241
+ lines.push(`\u0421\u0432\u043E\u0451 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 (\u0437\u0430\u043F\u043E\u043C\u043D\u0435\u043D\u043E, \u0443\u0436\u0435 \u0432 \u043A\u0430\u0436\u0434\u043E\u043C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0435; \xAB\u0437\u0430\u0431\u0443\u0434\u044C\xBB \u2014 plan_edit forget):
17242
+ ${mine.map((line) => `\u2014 ${line}`).join("\n")}`);
17243
+ }
16484
17244
  for (const said of data.warnings ?? []) lines.push(said);
16485
17245
  return lines.join("\n");
16486
17246
  }
@@ -16497,73 +17257,19 @@ async function askCatalog(engine, pack, video) {
16497
17257
  if (!answer.ok) return `\u0421\u043F\u0438\u0441\u043E\u043A \u0442\u043E\u0433\u043E, \u0447\u0442\u043E \u0443\u043C\u0435\u0435\u0442 \u043C\u043E\u043D\u0442\u0430\u0436, \u043D\u0435 \u043F\u0440\u0438\u0448\u0451\u043B: ${answer.text}`;
16498
17258
  let data;
16499
17259
  try {
16500
- data = JSON.parse(answer.text);
16501
- } catch {
16502
- return `\u0421\u043F\u0438\u0441\u043E\u043A \u0442\u043E\u0433\u043E, \u0447\u0442\u043E \u0443\u043C\u0435\u0435\u0442 \u043C\u043E\u043D\u0442\u0430\u0436, \u043D\u0435 \u043F\u0440\u0438\u0448\u0451\u043B: ${NOT_JSON}`;
16503
- }
16504
- if (data.contract !== CAPABILITIES_CONTRACT) {
16505
- return `\u0414\u0432\u0438\u0436\u043E\u043A \u0433\u043E\u0432\u043E\u0440\u0438\u0442 \u043D\u0430 \u0434\u043E\u0433\u043E\u0432\u043E\u0440\u0435 \xAB${String(data.contract)}\xBB, \u0430 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u0435 \u043F\u043E\u043D\u0438\u043C\u0430\u0435\u0442 \xAB${CAPABILITIES_CONTRACT}\xBB, \u0438 \u0441\u043F\u0438\u0441\u043E\u043A \u0442\u043E\u0433\u043E, \u0447\u0442\u043E \u0443\u043C\u0435\u0435\u0442 \u043C\u043E\u043D\u0442\u0430\u0436, \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D. \u041E\u0431\u043D\u043E\u0432\u0438 \u0442\u043E, \u0447\u0442\u043E \u0441\u0442\u0430\u0440\u0435\u0435: setup \u0441 what=engine \u0434\u043B\u044F \u0434\u0432\u0438\u0436\u043A\u0430, what=shell \u0434\u043B\u044F \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F.`;
16506
- }
16507
- return catalogInWords(data);
16508
- }
16509
- async function startMontage(engine, opts) {
16510
- const trouble = await engine.pythonTrouble();
16511
- if (trouble !== null) return toolError(trouble);
16512
- const python = engine.packPython();
16513
- if (python === null) {
16514
- return toolError(
16515
- "\u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u0435\u0449\u0451 \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u2014 \u043C\u043E\u043D\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C.\n\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0437\u043E\u0432\u0438 setup, \u043E\u043D \u0441\u043A\u0430\u0436\u0435\u0442, \u0447\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442, \u0430 \u0441 what \u043E\u043D \u044D\u0442\u043E \u0438 \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442."
16516
- );
16517
- }
16518
- const passport = await engine.passport();
16519
- if (passport === null) {
16520
- return toolError(
16521
- // ⚠ ВТОРАЯ ПРИЧИНА НАЗВАНА С 2026-09-09, И ОНА ТЕПЕРЬ ЧАСТАЯ. Пак
16522
- // переименован (`first-montage` → `montage`), а начинка живёт версионными
16523
- // папками: у того, чья начинка старее оболочки, пака по новому имени
16524
- // просто нет. `check_setup` про это не скажет ничего внятного — лечит
16525
- // обновление начинки, и назвать его надо здесь.
16526
- "\u041D\u0435 \u0441\u043C\u043E\u0433 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u043F\u0430\u0441\u043F\u043E\u0440\u0442 \u043F\u0430\u043A\u0430 \u043C\u043E\u043D\u0442\u0430\u0436\u0430 (pack.json).\n\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 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u043E\u0439 \u043D\u0435 \u0442\u0430\u043A. \u0415\u0441\u043B\u0438 \u043D\u0430\u0447\u0438\u043D\u043A\u0430 \u0441\u0442\u0430\u0432\u0438\u043B\u0430\u0441\u044C \u0434\u0430\u0432\u043D\u043E, \u0434\u0435\u043B\u043E \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u0432 \u043D\u0435\u0439: \u043F\u0430\u043A \u043F\u0435\u0440\u0435\u0435\u0445\u0430\u043B, \u0438 \u0441\u0442\u0430\u0440\u0430\u044F \u043D\u0430\u0447\u0438\u043D\u043A\u0430 \u0435\u0433\u043E \u043D\u0435 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u2014 \u0442\u043E\u0433\u0434\u0430 \u043F\u043E\u043C\u043E\u0433\u0430\u0435\u0442 setup \u0441 what=engine."
16527
- );
16528
- }
16529
- if (opts.tell !== void 0) {
16530
- for (const line of opts.ladder ?? montageLadder(passport["stages"])) opts.tell(line);
16531
- opts.tell(opts.hint);
16532
- }
16533
- const launch = await engine.background({
16534
- place: opts.place,
16535
- executable: python,
16536
- key: opts.key,
16537
- startJob: [
16538
- "job-create",
16539
- "--video",
16540
- opts.video,
16541
- "--what",
16542
- opts.what,
16543
- "--command",
16544
- JSON.stringify(opts.command),
16545
- ...opts.style !== void 0 ? ["--style", opts.style] : []
16546
- ],
16547
- args: (id) => [passport["entry"], opts.video, "--job", id, ...opts.args],
16548
- advice: "\u043F\u043E\u0437\u043E\u0432\u0438 setup \u2014 \u0441\u043A\u043E\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043E, \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043D\u0435\u043F\u043E\u043B\u043D\u043E\u0435"
16549
- });
16550
- if ("refusal" in launch) return toolError(launch.refusal);
16551
- return toolSuccess(
16552
- `\u0417\u0430\u0434\u0430\u0447\u0430 ${launch.id}: ${opts.what}
16553
- \u0412\u0441\u0451 \u0434\u0435\u043B\u0430\u0435\u0442\u0441\u044F \u043D\u0430 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F. ${opts.hint}
16554
- \u0420\u0430\u0431\u043E\u0442\u0430 \u043F\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0435\u0442 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B: \u043D\u043E\u043C\u0435\u0440 \u0437\u0430\u0434\u0430\u0447\u0438 \u0437\u0430\u043F\u043E\u043C\u043D\u0438 \u0438 \u043F\u043E\u0437\u043E\u0432\u0438 wait \u0441 job "${launch.id}" \u2014 \u043E\u043D \u043F\u043E\u0434\u043E\u0436\u0434\u0451\u0442 \u0438 \u0440\u0430\u0441\u0441\u043A\u0430\u0436\u0435\u0442 \u0445\u043E\u0434 \u043F\u043E \u0448\u0430\u0433\u0430\u043C, \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437\u044B\u0432\u0430\u0439 \u0435\u0433\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u0441\u0440\u0430\u0437\u0443.`,
16555
- startedJob({
16556
- id: launch.id,
16557
- what: opts.what,
16558
- ahead: (opts.ladder ?? montageLadder(passport["stages"])).join(" "),
16559
- message: opts.hint
16560
- })
16561
- );
17260
+ data = JSON.parse(answer.text);
17261
+ } catch {
17262
+ return `\u0421\u043F\u0438\u0441\u043E\u043A \u0442\u043E\u0433\u043E, \u0447\u0442\u043E \u0443\u043C\u0435\u0435\u0442 \u043C\u043E\u043D\u0442\u0430\u0436, \u043D\u0435 \u043F\u0440\u0438\u0448\u0451\u043B: ${NOT_JSON}`;
17263
+ }
17264
+ if (data.contract !== CAPABILITIES_CONTRACT) {
17265
+ return `\u0414\u0432\u0438\u0436\u043E\u043A \u0433\u043E\u0432\u043E\u0440\u0438\u0442 \u043D\u0430 \u0434\u043E\u0433\u043E\u0432\u043E\u0440\u0435 \xAB${String(data.contract)}\xBB, \u0430 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u0435 \u043F\u043E\u043D\u0438\u043C\u0430\u0435\u0442 \xAB${CAPABILITIES_CONTRACT}\xBB, \u0438 \u0441\u043F\u0438\u0441\u043E\u043A \u0442\u043E\u0433\u043E, \u0447\u0442\u043E \u0443\u043C\u0435\u0435\u0442 \u043C\u043E\u043D\u0442\u0430\u0436, \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D. \u041E\u0431\u043D\u043E\u0432\u0438 \u0442\u043E, \u0447\u0442\u043E \u0441\u0442\u0430\u0440\u0435\u0435: setup \u0441 what=engine \u0434\u043B\u044F \u0434\u0432\u0438\u0436\u043A\u0430, what=shell \u0434\u043B\u044F \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F.`;
17266
+ }
17267
+ return catalogInWords(data);
16562
17268
  }
16563
- function fromWords(text, contract) {
17269
+ function fromWords(text2, contract) {
16564
17270
  let data;
16565
17271
  try {
16566
- data = JSON.parse(text);
17272
+ data = JSON.parse(text2);
16567
17273
  } catch {
16568
17274
  return { refusal: NOT_JSON };
16569
17275
  }
@@ -16573,13 +17279,54 @@ function fromWords(text, contract) {
16573
17279
  if (typeof data.words !== "string" || data.words === "") return { refusal: NOT_JSON };
16574
17280
  return { words: data.words, data };
16575
17281
  }
16576
- function folderAnswer(text) {
16577
- const read = fromWords(text);
17282
+ function folderAnswer(text2) {
17283
+ const read = fromWords(text2);
16578
17284
  return "refusal" in read ? toolError(read.refusal) : toolSuccess(read.words, read.data);
16579
17285
  }
17286
+ var DRAFT_CONTRACT = "naparnik-draft/1";
17287
+ var VIEW_CONTRACT = "naparnik-view/1";
17288
+ var EDIT_CONTRACT = "naparnik-edit/1";
17289
+ function answerOrRefusal(text2, contract) {
17290
+ const read = fromWords(text2, contract);
17291
+ if ("refusal" in read) {
17292
+ const plain = text2.trim();
17293
+ return { refusal: plain.startsWith("{") || plain === "" ? read.refusal : plain };
17294
+ }
17295
+ return read.data["ok"] === false ? { refusal: read.words } : read;
17296
+ }
17297
+ async function draftDocument(engine, place, asked, key) {
17298
+ const answer = await engine.command([
17299
+ "document-draft",
17300
+ "--video",
17301
+ asked.video,
17302
+ "--pack",
17303
+ place.pack,
17304
+ ...asked.style !== void 0 ? ["--style", path(asked.style)] : [],
17305
+ ...(asked.also ?? []).flatMap((one) => ["--also", path(one)]),
17306
+ ...asked.ask !== void 0 ? ["--ask", JSON.stringify(asked.ask)] : [],
17307
+ "--json"
17308
+ ], key !== void 0 ? { key } : {});
17309
+ return answer.ok ? answerOrRefusal(answer.text, DRAFT_CONTRACT) : { refusal: answer.text };
17310
+ }
17311
+ async function editOperations(engine, place, asked, key) {
17312
+ const answer = await engine.command([
17313
+ "document-edit",
17314
+ "--project",
17315
+ path(asked.reel),
17316
+ "--ops",
17317
+ JSON.stringify(asked.ops),
17318
+ ...asked.stamps !== void 0 ? ["--stamps", JSON.stringify(asked.stamps)] : [],
17319
+ "--by",
17320
+ asked.by ?? "model",
17321
+ "--pack",
17322
+ place.pack,
17323
+ "--json"
17324
+ ], key !== void 0 ? { key } : {});
17325
+ return answer.ok ? answerOrRefusal(answer.text, EDIT_CONTRACT) : { refusal: answer.text };
17326
+ }
16580
17327
  function nameOrRefusal(candidate) {
16581
- const looksLikePath = candidate.includes("/") || candidate.includes("\\") || candidate.startsWith("~") || candidate.startsWith(".") || candidate.length > 1 && candidate[1] === ":";
16582
- if (!looksLikePath) return { name: candidate };
17328
+ const looksLikePath2 = candidate.includes("/") || candidate.includes("\\") || candidate.startsWith("~") || candidate.startsWith(".") || candidate.length > 1 && candidate[1] === ":";
17329
+ if (!looksLikePath2) return { name: candidate };
16583
17330
  return {
16584
17331
  refusal: `\u0417\u0434\u0435\u0441\u044C \u0436\u0434\u0443\u0442 \u0418\u041C\u042F \u0448\u0430\u0433\u0430, \u0430 \u043F\u0440\u0438\u0448\u0451\u043B \u043F\u0443\u0442\u044C: ${candidate}
16585
17332
  \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.`
@@ -16706,7 +17453,7 @@ ${EXTRAS_LATER}`), later]);
16706
17453
  if ("refusal" in launch) return toolError(launch.refusal);
16707
17454
  return toolSuccess(
16708
17455
  `\u0417\u0430\u0434\u0430\u0447\u0430 ${launch.id}: \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430, \u0448\u0430\u0433 \xAB${step}\xBB.
16709
- \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 \u0438 \u043F\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0435\u0442 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B. \u041F\u043E\u0437\u043E\u0432\u0438 wait \u0441 job "${launch.id}" \u2014 \u043E\u043D \u043F\u043E\u0434\u043E\u0436\u0434\u0451\u0442 \u0438 \u043F\u043E \u0445\u043E\u0434\u0443 \u0440\u0430\u0441\u0441\u043A\u0430\u0436\u0435\u0442, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u043A\u0430\u0447\u0430\u043D\u043E \u0438 \u043E \u0447\u0451\u043C \u043D\u0430\u0434\u043E \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430. \u041F\u0435\u0440\u0435\u0441\u043A\u0430\u0437\u044B\u0432\u0430\u0439 \u044D\u0442\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u0441\u0440\u0430\u0437\u0443, \u0430 \u043D\u0435 \u0432 \u043A\u043E\u043D\u0446\u0435.`,
17456
+ \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 \u0438 \u043F\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0435\u0442 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B. \u041F\u043E\u0437\u043E\u0432\u0438 wait \u0441 job "${launch.id}" \u2014 \u043E\u043D \u043F\u043E\u0434\u043E\u0436\u0434\u0451\u0442 \u0438 \u043F\u043E \u0445\u043E\u0434\u0443 \u0440\u0430\u0441\u0441\u043A\u0430\u0436\u0435\u0442, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u043A\u0430\u0447\u0430\u043D\u043E \u0438 \u043E \u0447\u0451\u043C \u043D\u0430\u0434\u043E \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430. \u041F\u0435\u0440\u0435\u0441\u043A\u0430\u0437\u044B\u0432\u0430\u0439 \u044D\u0442\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u0441\u0440\u0430\u0437\u0443, \u0430 \u043D\u0435 \u0432 \u043A\u043E\u043D\u0446\u0435. ${AWAY_NOTE}`,
16710
17457
  startedJob({
16711
17458
  id: launch.id,
16712
17459
  what: `\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430, \u0448\u0430\u0433 \xAB${step}\xBB`,
@@ -16729,9 +17476,9 @@ async function updateEngine(engine, consent, key, baseUrl, timeoutMs = ASKED_POI
16729
17476
  const outcome = await fetchManifest(manifestUrl(engine, baseUrl), timeoutMs, key);
16730
17477
  const decision = decide(engine.freshVersion(), outcome.ok ? outcome.manifest : null);
16731
17478
  if (decision.what === "nothing") {
16732
- const ownDir = engine.freshVersion();
17479
+ const ownDir2 = engine.freshVersion();
16733
17480
  return toolSuccess(
16734
- !outcome.ok ? `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C, \u0435\u0441\u0442\u044C \u043B\u0438 \u043D\u043E\u0432\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F \u043D\u0430\u0447\u0438\u043D\u043A\u0438: ${outcome.why}. \u042D\u0442\u043E \u043D\u0435 \u043F\u043E\u043B\u043E\u043C\u043A\u0430: \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F (${ownDir ?? "\u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430"}) \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043A\u0430\u043A \u0440\u0430\u0431\u043E\u0442\u0430\u043B\u0430.` : `\u041D\u0430\u0447\u0438\u043D\u043A\u0430 \u0441\u0432\u0435\u0436\u0430\u044F: \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 ${ownDir ?? "\u043D\u0435\u0442"}, \u043D\u043E\u0432\u0435\u0435 \u043D\u0435 \u0432\u044B\u0448\u043B\u043E.`
17481
+ !outcome.ok ? `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C, \u0435\u0441\u0442\u044C \u043B\u0438 \u043D\u043E\u0432\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F \u043D\u0430\u0447\u0438\u043D\u043A\u0438: ${outcome.why}. \u042D\u0442\u043E \u043D\u0435 \u043F\u043E\u043B\u043E\u043C\u043A\u0430: \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F (${ownDir2 ?? "\u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430"}) \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043A\u0430\u043A \u0440\u0430\u0431\u043E\u0442\u0430\u043B\u0430.` : `\u041D\u0430\u0447\u0438\u043D\u043A\u0430 \u0441\u0432\u0435\u0436\u0430\u044F: \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 ${ownDir2 ?? "\u043D\u0435\u0442"}, \u043D\u043E\u0432\u0435\u0435 \u043D\u0435 \u0432\u044B\u0448\u043B\u043E.`
16735
17482
  );
16736
17483
  }
16737
17484
  if (decision.what === "shell_too_old") {
@@ -16925,16 +17672,228 @@ async function askBeforeHeavy(engine, step, model, key, baseUrl) {
16925
17672
  ]));
16926
17673
  }
16927
17674
  function together(results) {
16928
- const text = results.map((one) => one.type === "success" ? wordsOf(one.content) : one.error).filter((words) => words !== "").join("\n\n");
17675
+ const text2 = results.map((one) => one.type === "success" ? wordsOf(one.content) : one.error).filter((words) => words !== "").join("\n\n");
16929
17676
  for (const one of results) {
16930
- if (one.type === "success" && one.structured !== void 0) return toolSuccess(text, one.structured);
17677
+ if (one.type === "success" && one.structured !== void 0) return toolSuccess(text2, one.structured);
16931
17678
  }
16932
- return results.some((one) => one.type === "error") ? toolError(text) : toolSuccess(text);
17679
+ return results.some((one) => one.type === "error") ? toolError(text2) : toolSuccess(text2);
16933
17680
  }
16934
17681
  function wordsOf(content) {
16935
17682
  if (typeof content === "string") return content;
16936
17683
  return content.map((block) => block.type === "text" ? block.text : "").join("\n");
16937
17684
  }
17685
+ async function startRender(engine, asked, key, tell) {
17686
+ const place = engine.place();
17687
+ if (place === null) return toolError(engine.whyNoEngine());
17688
+ const where = fileOrRefusal(asked.video);
17689
+ if ("refusal" in where) return toolError(where.refusal);
17690
+ if (asked.onlyPauses !== true) return startDocumentRender(engine, where.path, key, tell);
17691
+ if (tell !== void 0) for (const line of PAUSES_LADDER) tell(line);
17692
+ const ask2 = { pauses: {} };
17693
+ if (asked.tempo !== void 0 && asked.tempo !== 1) ask2["tempo"] = asked.tempo;
17694
+ const draft = await draftDocument(engine, place, { video: where.path, style: asked.style, ask: ask2 }, key);
17695
+ if ("refusal" in draft) return toolError(draft.refusal);
17696
+ const reel = String(draft.data["project"] ?? where.path);
17697
+ const agreement = await editOperations(engine, place, { reel, ops: [{ op: "agree" }] }, key);
17698
+ if ("refusal" in agreement) return toolError(agreement.refusal);
17699
+ return startDocumentRender(engine, reel, key, tell);
17700
+ }
17701
+ async function startDocumentRender(engine, reel, key, tell) {
17702
+ const place = engine.place();
17703
+ if (place === null) return toolError(engine.whyNoEngine());
17704
+ const trouble = await engine.pythonTrouble();
17705
+ if (trouble !== null) return toolError(trouble);
17706
+ const python = engine.packPython();
17707
+ if (python === null) {
17708
+ return toolError("\u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u0435\u0449\u0451 \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0437\u043E\u0432\u0438 setup.");
17709
+ }
17710
+ if (tell !== void 0) for (const line of RENDER_LADDER) tell(line);
17711
+ const what = `\u043C\u043E\u043D\u0442\u0430\u0436 ${basename(reel)}`;
17712
+ const launch = await engine.background({
17713
+ place,
17714
+ executable: python,
17715
+ key,
17716
+ startJob: [
17717
+ "job-create",
17718
+ "--video",
17719
+ reel,
17720
+ "--what",
17721
+ what,
17722
+ "--command",
17723
+ JSON.stringify({ tool: "render", args: {} })
17724
+ ],
17725
+ args: (id) => [
17726
+ place.consolePy,
17727
+ "document-render",
17728
+ "--project",
17729
+ reel,
17730
+ "--pack",
17731
+ place.pack,
17732
+ "--job",
17733
+ id
17734
+ ],
17735
+ advice: "\u043F\u043E\u0437\u043E\u0432\u0438 setup \u2014 \u0441\u043A\u043E\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043E, \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043D\u0435\u043F\u043E\u043B\u043D\u043E\u0435"
17736
+ });
17737
+ if ("refusal" in launch) return toolError(launch.refusal);
17738
+ return toolSuccess(
17739
+ `\u0417\u0430\u0434\u0430\u0447\u0430 ${launch.id}: ${what}
17740
+ \u0414\u0430\u043B\u044C\u0448\u0435 \u043F\u043E\u0437\u043E\u0432\u0438 wait \u0441 \u044D\u0442\u0438\u043C \u043D\u043E\u043C\u0435\u0440\u043E\u043C. ${AWAY_NOTE}`,
17741
+ startedJob({
17742
+ id: launch.id,
17743
+ what,
17744
+ ahead: RENDER_LADDER.join(" "),
17745
+ message: "\u0420\u043E\u043B\u0438\u043A \u0441\u043E\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044F \u043D\u0430 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F."
17746
+ })
17747
+ );
17748
+ }
17749
+ var RENDER_LADDER = [
17750
+ "\u041F\u0440\u043E\u0432\u0435\u0440\u044E, \u0447\u0442\u043E \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D \u0438 \u043D\u0435 \u043F\u0440\u0430\u0432\u043B\u0435\u043D \u043C\u0438\u043C\u043E plan_edit.",
17751
+ "\u041F\u043E\u0441\u0447\u0438\u0442\u0430\u044E \u044D\u0444\u0444\u0435\u043A\u0442\u044B \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A\u0430 (\u0432\u044B\u0440\u0435\u0437 \u0433\u0435\u0440\u043E\u044F, \u0448\u0443\u043C\u043E\u0434\u0430\u0432) \u0442\u0430\u043C, \u0433\u0434\u0435 \u0438\u0445 \u0432\u0438\u0434\u043D\u043E, \u2014 \u043F\u043E\u0442\u043E\u043C \u0441\u043E\u0431\u0435\u0440\u0443 \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443 \u0438 \u0437\u0432\u0443\u043A."
17752
+ ];
17753
+ async function followWork(engine, asked, pace = realPace, tell) {
17754
+ const ask2 = () => engine.command(["job", asked.job]);
17755
+ const seconds2 = waitSeconds(asked.seconds ?? WAIT_LIMIT_SECONDS);
17756
+ if (seconds2 === 0) return asJobResult(await ask2());
17757
+ const state = await followJob({
17758
+ ask: ask2,
17759
+ seconds: seconds2,
17760
+ pace,
17761
+ // ⚠ Рассказ есть только если КЛИЕНТ его попросил (токен хода работы в
17762
+ // `_meta`). Нет — ждём молча и отвечаем итогом: ждать всё равно дешевле
17763
+ // пяти кругов переписки «спроси ещё раз».
17764
+ ...tell !== void 0 ? { tell } : {}
17765
+ });
17766
+ if (state.ok && stillWorking(state.text)) {
17767
+ const words = `${state.text}
17768
+ \u0416\u0434\u0430\u043B ${seconds2} \u0441, \u0440\u0430\u0431\u043E\u0442\u0430 \u0435\u0449\u0451 \u0438\u0434\u0451\u0442. \u0421\u043A\u0430\u0436\u0438 \u044D\u0442\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u0438 \u043F\u043E\u0437\u043E\u0432\u0438 wait \u0435\u0449\u0451 \u0440\u0430\u0437 \u2014 \u0442\u0430\u043A \u043E\u043D \u0443\u0441\u043B\u044B\u0448\u0438\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0435 \u0448\u0430\u0433\u0438.`;
17769
+ const job = readJob(state.text);
17770
+ return job === null ? toolSuccess(words) : toolSuccess(words, job);
17771
+ }
17772
+ return asJobResult(state);
17773
+ }
17774
+ async function listJobs(engine, asked) {
17775
+ return asToolResult(
17776
+ await engine.command([
17777
+ "jobs",
17778
+ ...asked.video !== void 0 ? ["--video", path(asked.video)] : [],
17779
+ ...asked.limit !== void 0 ? ["--count", String(asked.limit)] : []
17780
+ ])
17781
+ );
17782
+ }
17783
+ function renderTool(engine, _passport, key) {
17784
+ return defineTool({
17785
+ name: "render",
17786
+ title: "\u0421\u043E\u0431\u0440\u0430\u0442\u044C \u0440\u043E\u043B\u0438\u043A",
17787
+ description: '\u0421\u043E\u0431\u0438\u0440\u0430\u0435\u0442 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0440\u043E\u043B\u0438\u043A\u0430 \u043D\u0430 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430: \u044D\u0444\u0444\u0435\u043A\u0442\u044B \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A\u0430 (\u0432\u044B\u0440\u0435\u0437 \u0433\u0435\u0440\u043E\u044F, \u0448\u0443\u043C\u043E\u0434\u0430\u0432), \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443, \u0437\u0432\u0443\u043A, \u0433\u043E\u0442\u043E\u0432\u044B\u0439 \u0444\u0430\u0439\u043B. \u0417\u043E\u0432\u0438 \u041F\u041E\u0421\u041B\u0415 \u0442\u043E\u0433\u043E, \u043A\u0430\u043A \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0441\u043A\u0430\u0437\u0430\u043B \xAB\u0434\u0430\xBB \u043D\u0430 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0438\u0437 plan \u0438 \u0442\u044B \u0437\u0430\u043F\u0438\u0441\u0430\u043B \u044D\u0442\u043E plan_edit \u0441 {"op": "agree"}. \u0421\u043E\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044F \u0422\u041E\u041B\u042C\u041A\u041E \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442, \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043D\u044B\u0439 \u0447\u0435\u0440\u0435\u0437 plan_edit: \u0444\u0430\u0439\u043B, \u043F\u043E\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043D\u044B\u0439 \u043C\u0438\u043C\u043E \u043D\u0435\u0433\u043E, \u0434\u0432\u0438\u0436\u043E\u043A \u043D\u0435 \u0441\u043E\u0431\u0435\u0440\u0451\u0442 \u2014 \xAB\u043F\u0440\u0438\u0448\u043B\u0438 \u043F\u0440\u0430\u0432\u043A\u0443 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044F\u043C\u0438\xBB.\n\u041E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043D\u043E\u043C\u0435\u0440\u043E\u043C \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0434\u0430\u043B\u044C\u0448\u0435 wait, \u043E\u043D \u043F\u043E\u0434\u043E\u0436\u0434\u0451\u0442 \u0438 \u0440\u0430\u0441\u0441\u043A\u0430\u0436\u0435\u0442 \u0445\u043E\u0434. \u041F\u043E\u0432\u0442\u043E\u0440 \u0431\u0435\u0437 \u043F\u0440\u0430\u0432\u043E\u043A \u043E\u0442\u0434\u0430\u0451\u0442 \u0433\u043E\u0442\u043E\u0432\u043E\u0435 \u0441\u0440\u0430\u0437\u0443.\n\u041F\u0440\u043E\u0441\u044F\u0442 \xAB\u043F\u0440\u043E\u0441\u0442\u043E \u0443\u0431\u0435\u0440\u0438 \u043F\u0430\u0443\u0437\u044B\xBB \u2014 only_pauses=true \u0441 video: \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0441 \u043E\u0434\u043D\u0438\u043C\u0438 \u043F\u0430\u0443\u0437\u0430\u043C\u0438, \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435 \u0438 \u0441\u0431\u043E\u0440\u043A\u0430 \u043E\u0434\u043D\u0438\u043C \u0432\u044B\u0437\u043E\u0432\u043E\u043C; tempo \u0440\u044F\u0434\u043E\u043C \u2014 \xAB\u0438 \u0443\u0441\u043A\u043E\u0440\u044C\xBB. \u041F\u0430\u0443\u0437\u044B \u0443\u0431\u0438\u0440\u0430\u044E\u0442\u0441\u044F \u043F\u043E \u0441\u043B\u043E\u0432\u0430\u043C, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043D\u0443\u0436\u043D\u0430 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u043D\u043D\u0430\u044F \u0440\u0435\u0447\u044C.\n\u0420\u0430\u0431\u043E\u0442\u0430 \u0438\u0434\u0451\u0442 \u043D\u0430 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430, \u043A \u043D\u0430\u043C \u0432\u0438\u0434\u0435\u043E \u043D\u0435 \u0443\u0445\u043E\u0434\u0438\u0442 \u2014 \u043D\u0430\u0432\u0435\u0440\u0445 \u0438\u0434\u0451\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438 \u043F\u043E \u043A\u043B\u044E\u0447\u0443.',
17788
+ // Полоса хода работы: монтаж идёт минутами, и всё это время в ленте не
17789
+ // происходило бы ничего. ⚠ Ожидание — у `wait`, и полосы там нет: иначе
17790
+ // каждая проверка рисовала бы новую поверх этой.
17791
+ ui: { resourceUri: RUNBAR_RESOURCE_URI },
17792
+ args: external_exports.object({
17793
+ video: external_exports.string().describe(
17794
+ "\u041F\u0443\u0442\u044C \u043A \u0432\u0438\u0434\u0435\u043E\u0444\u0430\u0439\u043B\u0443 \u0440\u043E\u043B\u0438\u043A\u0430. \u0417\u0430\u0432\u0435\u0434\u0451\u043D\u043D\u044B\u0439 \u0440\u043E\u043B\u0438\u043A \u043C\u043E\u0436\u043D\u043E \u043D\u0430\u0437\u0432\u0430\u0442\u044C \u0438 \u043F\u0430\u043F\u043A\u043E\u0439 \u2014 \u043A\u043B\u044E\u0447\u043E\u043C reel"
17795
+ ).optional(),
17796
+ reel: external_exports.string().describe(
17797
+ "\u041F\u0430\u043F\u043A\u0430 \u0440\u043E\u043B\u0438\u043A\u0430: \u0441\u043E\u0431\u0435\u0440\u0443 \u0435\u0433\u043E \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 reel.json"
17798
+ ).optional(),
17799
+ only_pauses: external_exports.boolean().describe(
17800
+ "\u0422\u043E\u043B\u044C\u043A\u043E \u0443\u0431\u0440\u0430\u0442\u044C \u043F\u0430\u0443\u0437\u044B: \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0441 \u043E\u0434\u043D\u0438\u043C \u0440\u0430\u0437\u0434\u0435\u043B\u043E\u043C pauses, \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435 \u0438 \u0441\u0431\u043E\u0440\u043A\u0430 \u0440\u0430\u0437\u043E\u043C"
17801
+ ).optional(),
17802
+ tempo: external_exports.number().describe(
17803
+ "\u0422\u043E\u043B\u044C\u043A\u043E \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 only_pauses: \u0443\u0441\u043A\u043E\u0440\u0438\u0442\u044C \u0440\u0435\u0447\u044C, 1.0 \u2014 \u0431\u0435\u0437 \u0443\u0441\u043A\u043E\u0440\u0435\u043D\u0438\u044F"
17804
+ ).optional(),
17805
+ style: external_exports.string().describe(
17806
+ "\u0422\u043E\u043B\u044C\u043A\u043E \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 only_pauses: \u043F\u0430\u043F\u043A\u0430 \u043F\u0430\u043A\u0430 \u0444\u0438\u0440\u043C\u0435\u043D\u043D\u043E\u0433\u043E \u0441\u0442\u0438\u043B\u044F"
17807
+ ).optional()
17808
+ }).check((ctx) => {
17809
+ const asked = ctx.value;
17810
+ const say = (message, where) => {
17811
+ ctx.issues.push({ code: "custom", message, input: asked, path: [where] });
17812
+ };
17813
+ if (asked.video === void 0 && asked.reel === void 0) {
17814
+ say(
17815
+ "\u041D\u0435 \u0441\u043A\u0430\u0437\u0430\u043D\u043E, \u0447\u0442\u043E \u0441\u043E\u0431\u0438\u0440\u0430\u0442\u044C: \u043D\u0430\u0437\u043E\u0432\u0438 video (\u0444\u0430\u0439\u043B) \u0438\u043B\u0438 reel (\u043F\u0430\u043F\u043A\u0443 \u0437\u0430\u0432\u0435\u0434\u0451\u043D\u043D\u043E\u0433\u043E \u0440\u043E\u043B\u0438\u043A\u0430). \u0427\u0442\u043E \u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0435\u0441\u0442\u044C, \u043F\u043E\u043A\u0430\u0436\u0435\u0442 plan \u0431\u0435\u0437 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u043E\u0432",
17816
+ "video"
17817
+ );
17818
+ }
17819
+ if (asked.video !== void 0 && asked.reel !== void 0) {
17820
+ say("\xABvideo\xBB \u0438 \xABreel\xBB \u2014 \u044D\u0442\u043E \u043E\u0434\u043D\u043E \u0438 \u0442\u043E \u0436\u0435 \u043C\u0435\u0441\u0442\u043E, \u043D\u0430\u0437\u0432\u0430\u043D\u043D\u043E\u0435 \u0434\u0432\u0430\u0436\u0434\u044B. \u041E\u0441\u0442\u0430\u0432\u044C \u043E\u0434\u043D\u043E", "reel");
17821
+ }
17822
+ if (asked.only_pauses === true) return;
17823
+ const extra = ["tempo", "style"].filter((name) => asked[name] !== void 0);
17824
+ if (extra.length > 0) {
17825
+ say(
17826
+ `${extra.join(", ")} \u2014 \u0447\u0430\u0441\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430, \u0430 \u043D\u0435 \u0441\u0431\u043E\u0440\u043A\u0438: \u0442\u0435\u043C\u043F \u2014 plan \u0441 ask {"tempo": \u2026} \u0438\u043B\u0438 plan_edit, \u0441\u0442\u0438\u043B\u044C \u2014 plan \u0441\u043E style. \u0417\u0434\u0435\u0441\u044C \u043E\u043D\u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 only_pauses`,
17827
+ extra[0] ?? "tempo"
17828
+ );
17829
+ }
17830
+ }),
17831
+ async execute(args, ctx) {
17832
+ return startRender(
17833
+ engine,
17834
+ {
17835
+ video: args.video ?? args.reel ?? "",
17836
+ onlyPauses: args.only_pauses,
17837
+ tempo: args.tempo,
17838
+ style: args.style
17839
+ },
17840
+ key,
17841
+ ctx?.emitProgress
17842
+ );
17843
+ }
17844
+ });
17845
+ }
17846
+ function waitTool(engine, pace = realPace) {
17847
+ return defineTool({
17848
+ name: "wait",
17849
+ title: "\u0414\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F \u0440\u0430\u0431\u043E\u0442\u044B",
17850
+ description: `\u0416\u0434\u0451\u0442 \u0437\u0430\u0434\u0430\u0447\u0443 \u0438 \u043F\u043E \u0445\u043E\u0434\u0443 \u0440\u0430\u0441\u0441\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u043A\u0430\u043A\u043E\u0439 \u0448\u0430\u0433 \u043F\u043E\u0448\u0451\u043B \u0438 \u043A\u0430\u043A\u043E\u0439 \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B\u0441\u044F \u2014 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437\u044B\u0432\u0430\u0439 \u044D\u0442\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u0441\u0440\u0430\u0437\u0443, \u043D\u0435 \u0434\u043E\u0436\u0438\u0434\u0430\u044F\u0441\u044C \u043A\u043E\u043D\u0446\u0430. \u0422\u0430\u043A \u0438 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0439: \u043E\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C \u043F\u043E \u043A\u0440\u0443\u0433\u0443 \u043D\u0435 \u043D\u0430\u0434\u043E, \u0436\u0434\u0451\u0442 \u043E\u043D \u0434\u043E ${WAIT_LIMIT_SECONDS} \u0441\u0435\u043A\u0443\u043D\u0434 \u0438 \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0440\u0430\u043D\u044C\u0448\u0435, \u0435\u0441\u043B\u0438 \u0440\u0430\u0431\u043E\u0442\u0430 \u043A\u043E\u043D\u0447\u0438\u0442\u0441\u044F.
17851
+ \u0411\u0435\u0437 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u043D\u043E\u043C\u0435\u0440, \u0440\u043E\u043B\u0438\u043A, \u0447\u0435\u043C \u043A\u043E\u043D\u0447\u0438\u043B\u043E\u0441\u044C. \u0417\u043E\u0432\u0438, \u043A\u043E\u0433\u0434\u0430 \u043D\u043E\u043C\u0435\u0440 \u043F\u043E\u0442\u0435\u0440\u044F\u043B\u0441\u044F: \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440, \u043F\u043E\u0441\u043B\u0435 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B \u0438\u043B\u0438 \u043A\u043E\u0433\u0434\u0430 \u0432\u044B\u0437\u043E\u0432 \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0441\u044F \u043F\u043E\u0441\u0440\u0435\u0434\u0438 \u0440\u0430\u0431\u043E\u0442\u044B. \u0421 video \u2014 \u0437\u0430\u0434\u0430\u0447\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0440\u043E\u043B\u0438\u043A\u0430.
17852
+ \u0417\u0430\u0434\u0430\u0447\u0438 \u043B\u0435\u0436\u0430\u0442 \u043D\u0430 \u0434\u0438\u0441\u043A\u0435 \u0438 \u043F\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u044E\u0442 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B. \u041F\u043E\u0434\u043F\u0438\u0441\u043A\u0430 \u043D\u0435 \u043D\u0443\u0436\u043D\u0430: \u0437\u0430\u0431\u0440\u0430\u0442\u044C \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435 \u043C\u043E\u0436\u043D\u043E \u0432\u0441\u0435\u0433\u0434\u0430.`,
17853
+ args: external_exports.object({
17854
+ job: external_exports.string().describe(
17855
+ "\u041D\u043E\u043C\u0435\u0440 \u0437\u0430\u0434\u0430\u0447\u0438, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 2026-08-19T14-03-12Z-a3f1. \u0415\u0433\u043E \u043D\u0430\u0437\u0432\u0430\u043B \u0442\u043E\u0442 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442, \u0447\u0442\u043E \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B \u0440\u0430\u0431\u043E\u0442\u0443"
17856
+ ).optional(),
17857
+ seconds: external_exports.number().describe(
17858
+ `\u0421\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0435\u043A\u0443\u043D\u0434 \u0436\u0434\u0430\u0442\u044C. \u041F\u043E\u0442\u043E\u043B\u043E\u043A ${WAIT_LIMIT_SECONDS}: \u043F\u0440\u043E\u0441\u044C\u0431\u0430 \u0431\u043E\u043B\u044C\u0448\u0435 \u043C\u043E\u043B\u0447\u0430 \u043E\u0431\u0440\u0435\u0436\u0435\u0442\u0441\u044F. \u0411\u0435\u0437 \u043D\u0435\u0433\u043E \u0436\u0434\u0451\u0442 \u0441\u0442\u043E\u043B\u044C\u043A\u043E, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043C\u043E\u0436\u043D\u043E`
17859
+ ).optional(),
17860
+ video: external_exports.string().describe("\u0422\u043E\u043B\u044C\u043A\u043E \u0437\u0430\u0434\u0430\u0447\u0438 \u044D\u0442\u043E\u0433\u043E \u0440\u043E\u043B\u0438\u043A\u0430 (\u0431\u0435\u0437 job)").optional(),
17861
+ limit: external_exports.number().describe("\u0421\u043A\u043E\u043B\u044C\u043A\u043E \u0437\u0430\u0434\u0430\u0447 \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C, \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E 15").optional()
17862
+ }).check((ctx) => {
17863
+ const asked = ctx.value;
17864
+ if (asked.job === void 0) {
17865
+ if (asked.seconds !== void 0) {
17866
+ ctx.issues.push({
17867
+ code: "custom",
17868
+ message: "\xABseconds\xBB \u2014 \u044D\u0442\u043E \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0436\u0434\u0430\u0442\u044C \u041E\u0414\u041D\u0423 \u0437\u0430\u0434\u0430\u0447\u0443: \u043D\u0430\u0437\u043E\u0432\u0438 \u0435\u0451 \u043D\u043E\u043C\u0435\u0440 \u0432 \xABjob\xBB",
17869
+ input: asked,
17870
+ path: ["seconds"]
17871
+ });
17872
+ }
17873
+ return;
17874
+ }
17875
+ for (const key of ["video", "limit"]) {
17876
+ if (asked[key] !== void 0) {
17877
+ ctx.issues.push({
17878
+ code: "custom",
17879
+ message: `\xAB${key}\xBB \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0441\u044F \u043A \u0421\u041F\u0418\u0421\u041A\u0423 \u0437\u0430\u0434\u0430\u0447, \u0430 \u0441 \xABjob\xBB \u0436\u0434\u0443\u0442 \u043E\u0434\u043D\u0443. \u041F\u043E\u0437\u043E\u0432\u0438 \u0431\u0435\u0437 \xABjob\xBB, \u0447\u0442\u043E\u0431\u044B \u0443\u0432\u0438\u0434\u0435\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A`,
17880
+ input: asked,
17881
+ path: [key]
17882
+ });
17883
+ }
17884
+ }
17885
+ }),
17886
+ readOnly: true,
17887
+ async execute(args, ctx) {
17888
+ const place = engine.place();
17889
+ if (place === null) return toolError(engine.whyNoEngine());
17890
+ if (args.job === void 0) {
17891
+ return listJobs(engine, { video: args.video, limit: args.limit });
17892
+ }
17893
+ return followWork(engine, { job: args.job, seconds: args.seconds }, pace, ctx?.emitProgress);
17894
+ }
17895
+ });
17896
+ }
16938
17897
  async function listReels(engine, workspace) {
16939
17898
  const place = engine.place();
16940
17899
  if (place === null) return toolError(engine.whyNoEngine());
@@ -16970,18 +17929,7 @@ async function openReel(engine, asked) {
16970
17929
  if (!answer.ok) return toolError(answer.text);
16971
17930
  return folderAnswer(answer.text);
16972
17931
  }
16973
- var PLAN_DRAFT = "naparnik-plan-draft/1";
16974
- var COMPOSITION_VIEW = "naparnik-composition-view/1";
16975
17932
  var SKILLS_READ = "naparnik-skills-read/1";
16976
- function answerOrRefusal(text, contract) {
16977
- const read = fromWords(text, contract);
16978
- if ("refusal" in read) {
16979
- const plain = text.trim();
16980
- return { refusal: plain.startsWith("{") || plain === "" ? read.refusal : plain };
16981
- }
16982
- const said = read.data;
16983
- return said.ok === false ? { refusal: read.words } : read;
16984
- }
16985
17933
  async function readSkills(engine, asked) {
16986
17934
  const place = engine.place();
16987
17935
  if (place === null) return toolError(engine.whyNoEngine());
@@ -16997,19 +17945,21 @@ async function readSkills(engine, asked) {
16997
17945
  const read = answerOrRefusal(answer.text, SKILLS_READ);
16998
17946
  return "refusal" in read ? toolError(read.refusal) : toolSuccess(read.words, read.data);
16999
17947
  }
17000
- async function viewComposition(engine, reel) {
17948
+ async function viewDocument(engine, reel) {
17001
17949
  const place = engine.place();
17002
17950
  if (place === null) return toolError(engine.whyNoEngine());
17003
17951
  const where = fileOrRefusal(reel);
17004
17952
  if ("refusal" in where) return toolError(where.refusal);
17005
17953
  const answer = await engine.command([
17006
- "composition-view",
17954
+ "document-view",
17007
17955
  "--project",
17008
17956
  path(where.path),
17957
+ "--pack",
17958
+ place.pack,
17009
17959
  "--json"
17010
17960
  ]);
17011
17961
  if (!answer.ok) return toolError(answer.text);
17012
- const read = answerOrRefusal(answer.text, COMPOSITION_VIEW);
17962
+ const read = answerOrRefusal(answer.text, VIEW_CONTRACT);
17013
17963
  return "refusal" in read ? toolError(read.refusal) : toolSuccess(read.words, read.data);
17014
17964
  }
17015
17965
  async function whereToDraft(engine, video, also, workspace) {
@@ -17076,95 +18026,55 @@ async function planDraft(engine, asked, key, tell) {
17076
18026
  }
17077
18027
  }
17078
18028
  if (tell !== void 0) for (const line of DRAFT_LADDER) tell(line);
17079
- const head = opened !== null && opened.type === "success" && typeof opened.content === "string" ? `${opened.content}
18029
+ const head2 = opened !== null && opened.type === "success" && typeof opened.content === "string" ? `${opened.content}
17080
18030
 
17081
18031
  ` : "";
17082
- const withHead = (text) => `${head}${text}`;
17083
- const answer = await engine.command([
17084
- "plan-draft",
17085
- "--video",
17086
- plan.speech,
17087
- "--pack",
17088
- place.pack,
17089
- ...asked.style !== void 0 ? ["--style", path(asked.style)] : [],
17090
- "--json"
17091
- ], key !== void 0 ? { key } : {});
17092
- if (!answer.ok) return toolError(withHead(answer.text));
17093
- const read = answerOrRefusal(answer.text, PLAN_DRAFT);
18032
+ const withHead = (text2) => `${head2}${text2}`;
18033
+ const also = plan.toOpen.length > 0 ? [] : asked.also ?? [];
18034
+ const read = await draftDocument(
18035
+ engine,
18036
+ place,
18037
+ { video: plan.speech, style: asked.style, ask: asked.ask, also },
18038
+ key
18039
+ );
17094
18040
  if ("refusal" in read) return toolError(withHead(read.refusal));
17095
- return toolSuccess(withHead(read.words), read.data);
18041
+ return withSheets(withHead(read.words), read.data);
18042
+ }
18043
+ async function withSheets(text2, data) {
18044
+ const material = data["material"];
18045
+ const images = Array.isArray(material?.images) ? material.images.filter((one) => typeof one === "string") : [];
18046
+ if (images.length === 0) return toolSuccess(text2, data);
18047
+ const pictures = [];
18048
+ const lost = [];
18049
+ for (const file2 of images) {
18050
+ try {
18051
+ pictures.push({ type: "image", base64: (await readFile(file2)).toString("base64"), mediaType: "image/jpeg" });
18052
+ } catch {
18053
+ lost.push(basename(file2));
18054
+ }
18055
+ }
18056
+ const told = lost.length === 0 ? text2 : `${text2}
18057
+ \u26A0 \u0421\u0435\u0442\u043A\u0430 \u0440\u0430\u0441\u043A\u0430\u0434\u0440\u043E\u0432\u043A\u0438 \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043B\u0430\u0441\u044C: ${lost.join(", ")}. \u041A\u0430\u0434\u0440 \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A\u0430 \u043F\u043E\u043A\u0430\u0436\u0435\u0442 preview \u0441 video, moment \u0438 from_where=source.`;
18058
+ return toolMixed([{ type: "text", text: told }, ...pictures], data);
17096
18059
  }
17097
18060
  var DRAFT_LADDER = [
17098
- "\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0440\u0430\u0437\u0431\u0435\u0440\u0443 \u0440\u0435\u0447\u044C: \u0443\u0431\u0435\u0440\u0443 \u043F\u0430\u0443\u0437\u044B, \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u044E \u0441\u043B\u043E\u0432\u0430, \u0440\u0430\u0437\u043B\u043E\u0436\u0443 \u0438\u0445 \u043D\u0430 \u0444\u0440\u0430\u0437\u044B.",
17099
- "\u041F\u043E\u0442\u043E\u043C \u043F\u0440\u0438\u0434\u0443\u043C\u0430\u044E, \u0447\u0442\u043E \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0438 \u043A\u043E\u0433\u0434\u0430, \u2014 \u0438 \u043F\u043E\u043A\u0430\u0436\u0443 \u044D\u0442\u043E \u0441\u043B\u043E\u0432\u0430\u043C\u0438, \u0434\u043E \u0441\u0431\u043E\u0440\u043A\u0438.",
17100
- "\u041F\u0435\u0440\u0432\u044B\u0439 \u0440\u0430\u0437 \u0438\u0434\u0451\u0442 \u043C\u0438\u043D\u0443\u0442\u0430\u043C\u0438: \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0447\u0438 \u0441\u0430\u043C\u043E\u0435 \u0434\u043E\u043B\u0433\u043E\u0435. \u041E\u0431\u043E\u0440\u0432\u0451\u0442\u0441\u044F \u2014 \u043F\u043E\u0437\u043E\u0432\u0438 wait, \u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442\u0441\u044F, \u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u044B\u0439 \u043F\u043B\u0430\u043D \u0441\u0442\u043E\u0438\u0442 \u0441\u0435\u043A\u0443\u043D\u0434."
18061
+ "\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u044E \u0440\u0435\u0447\u044C \u2014 \u0435\u0441\u043B\u0438 \u043F\u0440\u043E\u0441\u044C\u0431\u0435 \u043E\u043D\u0430 \u043D\u0443\u0436\u043D\u0430.",
18062
+ "\u041F\u043E\u0442\u043E\u043C \u0441\u043E\u0431\u0435\u0440\u0443 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430: \u043F\u0430\u0443\u0437\u044B, \u043F\u043E\u0434\u043F\u0438\u0441\u0438, \u043A\u0440\u0443\u043F\u043D\u043E\u0441\u0442\u0438, \u0437\u0432\u0443\u043A \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E, \u0447\u0442\u043E \u043F\u0440\u043E\u0441\u0438\u043B\u0438.",
18063
+ "\u041F\u0435\u0440\u0432\u044B\u0439 \u0440\u0430\u0437 \u0438\u0434\u0451\u0442 \u043C\u0438\u043D\u0443\u0442\u0430\u043C\u0438: \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0447\u0438 \u0441\u0430\u043C\u043E\u0435 \u0434\u043E\u043B\u0433\u043E\u0435. \u041E\u0431\u043E\u0440\u0432\u0451\u0442\u0441\u044F \u2014 \u043F\u043E\u0437\u043E\u0432\u0438 wait, \u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442\u0441\u044F, \u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u044B\u0439 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0441\u0442\u043E\u0438\u0442 \u0441\u0435\u043A\u0443\u043D\u0434."
17101
18064
  ];
17102
- function aboutAgreement(said) {
17103
- const lines = [];
17104
- if (said.agreed === true) lines.push("\u041F\u043B\u0430\u043D \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D \u2014 \u043C\u043E\u0436\u043D\u043E \u0441\u043E\u0431\u0438\u0440\u0430\u0442\u044C.");
17105
- if (said.agreed === false) {
17106
- lines.push(
17107
- "\u0421\u043E\u0433\u043B\u0430\u0441\u0438\u044F \u043D\u0435\u0442: \u044D\u0442\u043E\u0439 \u0440\u0435\u0434\u0430\u043A\u0446\u0438\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0435\u0449\u0451 \u043D\u0435 \u0432\u0438\u0434\u0435\u043B. \u041F\u0435\u0440\u0435\u0441\u043A\u0430\u0436\u0438 \u0435\u043C\u0443, \u0447\u0442\u043E \u0432\u044B\u0448\u043B\u043E, \u0438 \u043D\u0430 \xAB\u0434\u0430\xBB \u043F\u043E\u0437\u043E\u0432\u0438 plan_edit \u0441 confirm \u2014 \u043C\u043E\u0436\u043D\u043E \u0442\u0435\u043C \u0436\u0435 \u0432\u044B\u0437\u043E\u0432\u043E\u043C, \u0447\u0442\u043E \u0438 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0443\u044E \u043F\u0440\u0430\u0432\u043A\u0443."
17108
- );
17109
- }
17110
- if (said.plan_mine === true) {
17111
- lines.push(
17112
- "\u041F\u043B\u0430\u043D \u0440\u043E\u043B\u0438\u043A\u0430 \u0442\u0435\u043F\u0435\u0440\u044C \u0422\u0412\u041E\u0419: \u0441\u043E\u0431\u0438\u0440\u0430\u0442\u044C\u0441\u044F \u0431\u0443\u0434\u0435\u0442 \u043E\u043D, \u043D\u0430\u0448 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0435\u0442\u0441\u044F. \u0412\u0435\u0440\u043D\u0443\u0442\u044C \u043D\u0430\u0448 \u2014 plan_edit \u0441 draft."
17113
- );
17114
- }
17115
- return lines;
17116
- }
17117
- async function editPlan(engine, asked, key, tell) {
18065
+ async function editDocument(engine, asked, key, tell) {
17118
18066
  const place = engine.place();
17119
18067
  if (place === null) return toolError(engine.whyNoEngine());
17120
- const asked_ = await engine.command([
17121
- "plan-edit",
17122
- "--project",
17123
- path(asked.reel),
17124
- ...asked.ops.length > 0 ? ["--ops", JSON.stringify(asked.ops)] : [],
17125
- ...asked.plan !== void 0 ? ["--plan", JSON.stringify(asked.plan)] : [],
17126
- ...asked.project !== void 0 ? ["--composition", JSON.stringify(asked.project)] : [],
17127
- ...asked.draft === true ? ["--draft"] : [],
17128
- ...asked.confirm === true ? ["--confirm"] : [],
17129
- "--by",
17130
- asked.by ?? "model",
17131
- "--json"
17132
- ], key !== void 0 ? { key } : {});
17133
- if (!asked_.ok) return toolError(asked_.text);
17134
- let said;
17135
- try {
17136
- said = JSON.parse(lastLine(asked_.text));
17137
- } catch {
17138
- return toolSuccess(asked_.text);
17139
- }
17140
- const many = (said.overrides ?? []).length;
17141
- const done = typeof said.words === "string" && said.words !== "" ? said.words : [
17142
- `\u041F\u0440\u0430\u0432\u043E\u043A \u0432 \u043F\u043B\u0430\u043D\u0435 \u0440\u043E\u043B\u0438\u043A\u0430: ${many}. \u0420\u043E\u043B\u0438\u043A: ${said.project ?? asked.reel}`,
17143
- ...aboutAgreement(said)
17144
- ].join("\n");
17145
- if (asked.rebuild !== true) {
17146
- return toolSuccess(
17147
- `${done}
17148
- \u0427\u0442\u043E\u0431\u044B \u0443\u0432\u0438\u0434\u0435\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442, \u043F\u043E\u0437\u043E\u0432\u0438 plan_edit \u0435\u0449\u0451 \u0440\u0430\u0437 \u0441 rebuild \u0438\u043B\u0438 \u0441\u043E\u0431\u0435\u0440\u0438 render \u2014 \u043F\u0435\u0440\u0435\u0441\u0431\u043E\u0440\u043A\u0430 \u0438\u0437 \u043A\u044D\u0448\u0430 \u0441\u0442\u043E\u0438\u0442 \u0441\u0435\u043A\u0443\u043D\u0434.`
17149
- );
17150
- }
17151
- if (said.video === null || said.video === void 0) {
17152
- return toolError(
17153
- `${done}
17154
- \u0410 \u043F\u0435\u0440\u0435\u0441\u043E\u0431\u0440\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C: \u0432 \u0431\u0440\u0438\u0444\u0435 \u0440\u043E\u043B\u0438\u043A\u0430 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D \u0444\u0430\u0439\u043B \u0441 \u0440\u0435\u0447\u044C\u044E.
17155
- \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0437\u043E\u0432\u0438 render \u0441 \u043F\u0443\u0442\u0451\u043C \u043A \u0432\u0438\u0434\u0435\u043E \u2014 \u043E\u043D \u0438 \u0437\u0430\u043F\u0438\u0448\u0435\u0442 \u0444\u0430\u0439\u043B \u0432 \u0431\u0440\u0438\u0444.`
17156
- );
17157
- }
17158
- return startMontage(engine, {
17159
- key,
17160
- place,
17161
- ...tell !== void 0 ? { tell } : {},
17162
- video: said.video,
17163
- what: `\u043F\u0440\u0430\u0432\u043A\u0430 ${basename(said.video)}`,
17164
- command: { "tool": "montage", "args": {} },
17165
- args: [],
17166
- hint: "\u041F\u0440\u0430\u0432\u043A\u0430 \u043F\u043B\u0430\u043D\u0430: \u0437\u0430\u043D\u043E\u0432\u043E \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u0440\u043E\u043D\u0443\u0442\u043E\u0435, \u043E\u0431\u044B\u0447\u043D\u043E \u0441\u0435\u043A\u0443\u043D\u0434\u044B."
17167
- });
18068
+ const read = await editOperations(engine, place, asked, key);
18069
+ if ("refusal" in read) return toolError(read.refusal);
18070
+ if (asked.rebuild !== true) return toolSuccess(read.words, read.data);
18071
+ const project = typeof read.data["project"] === "string" ? read.data["project"] : asked.reel;
18072
+ const started = await startDocumentRender(engine, project, key, tell);
18073
+ if (started.type === "error") return started;
18074
+ const text2 = typeof started.content === "string" ? started.content : "";
18075
+ return toolSuccess(`${read.words}
18076
+
18077
+ ${text2}`, started.structured);
17168
18078
  }
17169
18079
  var RECIPE_CONTRACT = "naparnik-recipe-view/1";
17170
18080
  var STEP_NEEDS = {
@@ -17234,7 +18144,11 @@ function planTool(engine, key) {
17234
18144
  return defineTool({
17235
18145
  name: "plan",
17236
18146
  title: "\u0427\u0442\u043E \u043F\u043E\u043A\u0430\u0436\u0435\u043C \u0432 \u0440\u043E\u043B\u0438\u043A\u0435",
17237
- description: "\u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0440\u043E\u043B\u0438\u043A\u0430 \u0421\u041B\u041E\u0412\u0410\u041C\u0418 \u2014 \u0434\u043E \u0441\u0431\u043E\u0440\u043A\u0438. \u0411\u0435\u0437 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u0447\u0442\u043E \u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0432 \u043F\u0430\u043F\u043A\u0435: \u0440\u043E\u043B\u0438\u043A\u0438 \u0441 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435\u043C \u0438 \u0444\u0430\u0439\u043B\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0440\u043E\u043B\u0438\u043A\u043E\u043C \u0435\u0449\u0451 \u043D\u0435 \u0441\u0442\u0430\u043B\u0438. \u0417\u043E\u0432\u0438 \u043F\u0435\u0440\u0432\u044B\u043C, \u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0437\u043D\u0430\u0435\u0448\u044C, \u043E \u043A\u0430\u043A\u043E\u043C \u0440\u043E\u043B\u0438\u043A\u0435 \u0440\u0435\u0447\u044C: \u0434\u043E\u0433\u0430\u0434\u043A\u0430 \u0442\u0443\u0442 \u0434\u043E\u0440\u043E\u0436\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u0430.\n\u0421 video \u0437\u0430\u0432\u043E\u0434\u0438\u0442 \u0440\u043E\u043B\u0438\u043A (\u0435\u0441\u043B\u0438 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B \u0435\u0449\u0451 \u043D\u0435 \u0432 \u0440\u043E\u043B\u0438\u043A\u0435) \u0438 \u043F\u0440\u0438\u0434\u0443\u043C\u044B\u0432\u0430\u0435\u0442 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A: \u0447\u0442\u043E \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u043C \u0438 \u043A\u043E\u0433\u0434\u0430, \u043D\u043E\u043C\u0435\u0440\u0430 \u0441\u043B\u043E\u0432 \u043D\u0430 \u0433\u0440\u0430\u043D\u0438\u0446\u0430\u0445, \u0438\u043C\u044F \u043A\u0430\u0436\u0434\u043E\u0439 \u0432\u0441\u0442\u0430\u0432\u043A\u0438, \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u0435 \u0441\u043B\u043E\u0432\u0430\u043C\u0438 \u0438 \u043F\u0443\u0441\u0442\u044B\u0435 \u043F\u043E\u0434\u043F\u0438\u0441\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043D\u0430\u0434\u043E \u043F\u0440\u0438\u0434\u0443\u043C\u0430\u0442\u044C. \u0422\u0435\u043C \u0436\u0435 \u043E\u0442\u0432\u0435\u0442\u043E\u043C \u043D\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u0427\u0422\u041E \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u043F\u043E\u0434 \u044D\u0442\u043E\u0442 \u0440\u043E\u043B\u0438\u043A: \u043F\u0440\u043E\u0447\u0442\u0438 \u044D\u0442\u043E (plan \u0441 read), \u043E\u0431\u0441\u0443\u0434\u0438 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0441 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u043E\u043C, \u043F\u043E\u043F\u0440\u0430\u0432\u044C plan_edit \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u0435\u0433\u043E \xAB\u0434\u0430\xBB \u0437\u043E\u0432\u0438 render.\n\u041F\u0435\u0440\u0432\u044B\u0439 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0438\u0434\u0451\u0442 \u043C\u0438\u043D\u0443\u0442\u0430\u043C\u0438: \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0451\u0442\u0441\u044F \u0440\u0435\u0447\u044C. \u041E\u0431\u043E\u0440\u0432\u0430\u043B\u0441\u044F \u0432\u044B\u0437\u043E\u0432 \u2014 \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u043F\u0440\u043E\u043F\u0430\u043B\u0430: \u043F\u043E\u0437\u043E\u0432\u0438 wait, \u0430 \u043F\u043E\u0442\u043E\u043C plan \u043F\u043E \u0442\u043E\u043C\u0443 \u0436\u0435 \u0440\u043E\u043B\u0438\u043A\u0443 \u0435\u0449\u0451 \u0440\u0430\u0437, \u0432\u0442\u043E\u0440\u043E\u0439 \u0440\u0430\u0437 \u044D\u0442\u043E \u0441\u0435\u043A\u0443\u043D\u0434\u044B.\n\u0421 reel \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0439 \u043E\u0431\u0449\u0438\u0439 naparnik-project/1 \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u0438 \u0433\u043E\u0432\u043E\u0440\u0438\u0442, \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u0430 \u043B\u0438 \u0438\u043C\u0435\u043D\u043D\u043E \u044D\u0442\u0430 \u0440\u0435\u0434\u0430\u043A\u0446\u0438\u044F.\n\u0421 read \u043E\u0442\u0434\u0430\u0451\u0442 \u0442\u0435\u043A\u0441\u0442 \u043D\u0430\u0437\u0432\u0430\u043D\u043D\u044B\u0445 \u0444\u0430\u0439\u043B\u043E\u0432 \u0441\u043A\u0438\u043B\u043B\u043E\u0432 \u0438 \u0437\u043D\u0430\u043D\u0438\u0439 \u2014 \u0442\u0435\u0445, \u0447\u0442\u043E \u043D\u0430\u0437\u0432\u0430\u043B \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A. \u0427\u0438\u0442\u0430\u0439 \u043F\u043E 3\u20134 \u0437\u0430 \u0432\u044B\u0437\u043E\u0432 \u0438 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u043F\u0443\u0442\u0435\u0439: \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E, \u0447\u0442\u043E \u043D\u0430\u0437\u0432\u0430\u043D\u043E.",
18147
+ description: `\u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0440\u043E\u043B\u0438\u043A\u0430 \u2014 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u043C\u043E\u043D\u0442\u0430\u0436\u0430 reel.json \u0438 \u0435\u0433\u043E \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 \u0441\u043B\u043E\u0432\u0430\u043C\u0438, \u0434\u043E \u0441\u0431\u043E\u0440\u043A\u0438. \u0411\u0435\u0437 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u0447\u0442\u043E \u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0432 \u043F\u0430\u043F\u043A\u0435: \u0440\u043E\u043B\u0438\u043A\u0438 \u0441 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435\u043C \u0438 \u0444\u0430\u0439\u043B\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0440\u043E\u043B\u0438\u043A\u043E\u043C \u0435\u0449\u0451 \u043D\u0435 \u0441\u0442\u0430\u043B\u0438. \u0417\u043E\u0432\u0438 \u043F\u0435\u0440\u0432\u044B\u043C, \u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0437\u043D\u0430\u0435\u0448\u044C, \u043E \u043A\u0430\u043A\u043E\u043C \u0440\u043E\u043B\u0438\u043A\u0435 \u0440\u0435\u0447\u044C.
18148
+ \u0421 video \u0437\u0430\u0432\u043E\u0434\u0438\u0442 \u0440\u043E\u043B\u0438\u043A (\u0435\u0441\u043B\u0438 \u0444\u0430\u0439\u043B \u0435\u0449\u0451 \u043D\u0435 \u0432 \u0440\u043E\u043B\u0438\u043A\u0435) \u0438 \u0441\u043E\u0431\u0438\u0440\u0430\u0435\u0442 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A: \u043F\u043E\u0441\u0442\u0430\u0432\u0449\u0438\u043A\u0438 \u043A\u043B\u0430\u0434\u0443\u0442 \u043A\u043B\u0438\u043F\u044B \u043F\u043E \u0440\u0430\u0437\u0434\u0435\u043B\u0430\u043C \u043F\u0440\u043E\u0441\u044C\u0431\u044B. \u0423\u0437\u043A\u0430\u044F \u043F\u0440\u043E\u0441\u044C\u0431\u0430 \u2014 ask \u0441 \u0435\u0451 \u0440\u0430\u0437\u0434\u0435\u043B\u0430\u043C\u0438: {"tempo": 1.2} \u2014 \xAB\u0443\u0441\u043A\u043E\u0440\u044C \u0432 1,2 \u0440\u0430\u0437\u0430\xBB (\u0447\u0438\u0441\u043B\u043E \u043D\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A), {"loudness": {}, "voice": {}} \u2014 \xAB\u043F\u043E\u0434\u043D\u0438\u043C\u0438 \u0433\u0440\u043E\u043C\u043A\u043E\u0441\u0442\u044C \u0438 \u0443\u0431\u0435\u0440\u0438 \u0448\u0443\u043C\xBB; \u0440\u0430\u0437\u0434\u0435\u043B \u0441\u043E \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435\u043C true \u2014 \xAB\u043A\u0430\u043A \u0440\u0435\u0448\u0438\u0442 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\xBB. ask \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D: \xAB\u0441\u043C\u043E\u043D\u0442\u0438\u0440\u0443\u0439\xBB \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u2014 ${FULL_WORDS}. \u041F\u043E\u043B\u043D\u044B\u0439 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0438 \u043F\u0440\u043E\u0441\u044C\u0431\u0430 \u043F\u0440\u043E \u043A\u0430\u0434\u0440 \u043F\u0440\u0438\u043D\u043E\u0441\u044F\u0442 \u043C\u0430\u0442\u0435\u0440\u0438\u0430\u043B: \u0444\u0430\u0439\u043B\u044B \u0441\u043B\u043E\u0432\u0430\u043C\u0438 \u0438 \u0440\u0430\u0441\u043A\u0430\u0434\u0440\u043E\u0432\u043A\u0443 \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0430\u043C\u0438 \u2014 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 \u0434\u043E \u043F\u043B\u0430\u043D\u0430. \u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u043C \u0431\u043B\u043E\u043A\u043E\u043C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043D\u0430\u0437\u044B\u0432\u0430\u0435\u0442 2\u20134 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0441\u043F\u0440\u0430\u0432\u043E\u0447\u043D\u0438\u043A\u0430: \u043F\u0440\u043E\u0447\u0442\u0438 \u0438\u0445 (plan \u0441 read), \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0436\u0438 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u043F\u043E\u0441\u0442\u0440\u043E\u0447\u043D\u043E, \u043F\u043E\u043F\u0440\u0430\u0432\u044C plan_edit \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044F\u043C\u0438 \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u0435\u0433\u043E \xAB\u0434\u0430\xBB \u0437\u043E\u0432\u0438 render.
18149
+ \u041F\u0435\u0440\u0432\u044B\u0439 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0441 \u0440\u0435\u0447\u044C\u044E \u0438\u0434\u0451\u0442 \u043C\u0438\u043D\u0443\u0442\u0430\u043C\u0438. \u041E\u0431\u043E\u0440\u0432\u0430\u043B\u0441\u044F \u0432\u044B\u0437\u043E\u0432 \u2014 \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u043F\u0440\u043E\u043F\u0430\u043B\u0430: \u043F\u043E\u0437\u043E\u0432\u0438 wait, \u0430 \u043F\u043E\u0442\u043E\u043C plan \u043F\u043E \u0442\u043E\u043C\u0443 \u0436\u0435 \u0440\u043E\u043B\u0438\u043A\u0443 \u0435\u0449\u0451 \u0440\u0430\u0437, \u0432\u0442\u043E\u0440\u043E\u0439 \u0440\u0430\u0437 \u044D\u0442\u043E \u0441\u0435\u043A\u0443\u043D\u0434\u044B.
18150
+ \u0421 reel \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0440\u043E\u043B\u0438\u043A\u0430: \u0434\u043E\u0440\u043E\u0436\u043A\u0438, \u043A\u043B\u0438\u043F\u044B \u043F\u043E id, \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D \u043B\u0438 \u043E\u043D.
18151
+ \u0421 read \u043E\u0442\u0434\u0430\u0451\u0442 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0421\u043F\u0440\u0430\u0432\u043E\u0447\u043D\u0438\u043A\u0430 \u043C\u043E\u043D\u0442\u0430\u0436\u0451\u0440\u0430 (\u043F\u0435\u0447\u0430\u0442\u044C \u043F\u0435\u0440\u0432\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439), \u0442\u0430\u0431\u043B\u0438\u0446\u044B \u0432\u043A\u0443\u0441\u0430 \u0438 \u0447\u0443\u0436\u0438\u0435 \u0444\u0430\u0439\u043B\u044B \u2014 \u043F\u0443\u0442\u0438 \u0438\u0437 \u043E\u0433\u043B\u0430\u0432\u043B\u0435\u043D\u0438\u044F (\u0435\u0433\u043E \u0434\u0430\u0451\u0442 setup), \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0430 \u0438\u043B\u0438 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B. \u0427\u0438\u0442\u0430\u0439 2\u20134 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u043E\u0434\u043D\u0438\u043C \u0432\u044B\u0437\u043E\u0432\u043E\u043C \u0438 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u043F\u0443\u0442\u0435\u0439.`,
17238
18152
  // Полоса хода работы в ленте: черновик считается минутами, и всё это время
17239
18153
  // в ленте не происходило бы ничего. ⚠ Ожидание задачи — у `wait`, и полосы
17240
18154
  // у него нет: она объявлена на инструменте, и каждый вызов ожидания рисовал
@@ -17245,7 +18159,14 @@ function planTool(engine, key) {
17245
18159
  "\u041F\u0443\u0442\u044C \u043A \u0432\u0438\u0434\u0435\u043E\u0444\u0430\u0439\u043B\u0443 \u0441 \u0440\u0435\u0447\u044C\u044E \u2014 \u043F\u043E \u043D\u0435\u043C\u0443 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A. \u0424\u0430\u0439\u043B \u043B\u0435\u0436\u0438\u0442 \u0432 \u043F\u0430\u043F\u043A\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0438 \u0440\u043E\u043B\u0438\u043A\u043E\u043C \u0435\u0449\u0451 \u043D\u0435 \u0441\u0442\u0430\u043B \u2014 \u0437\u0430\u0432\u0435\u0434\u0443 \u0440\u043E\u043B\u0438\u043A \u0441\u0430\u043C"
17246
18160
  ).optional(),
17247
18161
  reel: external_exports.string().describe(
17248
- "\u041F\u0430\u043F\u043A\u0430 \u0437\u0430\u0432\u0435\u0434\u0451\u043D\u043D\u043E\u0433\u043E \u0440\u043E\u043B\u0438\u043A\u0430 \u0441 \u043E\u0431\u0449\u0438\u043C naparnik-project/1. \u041F\u043E\u043A\u0430\u0436\u0443 \u0442\u043E\u0447\u043D\u044B\u0439 JSON \u0438 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u044F; \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u044E"
18162
+ "\u041F\u0430\u043F\u043A\u0430 \u0437\u0430\u0432\u0435\u0434\u0451\u043D\u043D\u043E\u0433\u043E \u0440\u043E\u043B\u0438\u043A\u0430: \u043F\u043E\u043A\u0430\u0436\u0443 \u0435\u0433\u043E \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 reel.json \u2014 \u0434\u043E\u0440\u043E\u0436\u043A\u0438, \u043A\u043B\u0438\u043F\u044B \u043F\u043E id \u0438 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D \u043B\u0438 \u043E\u043D; \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u044E"
18163
+ ).optional(),
18164
+ // ⚠ ПОЛЯ РАЗДЕЛОВ СВОБОДНЫЕ, И ЭТО НЕ ЛЕНЬ: дом просьбы — поставщики пака
18165
+ // (`SECTIONS` шагов и их `supply`), судья — движок, и отказ приходит его
18166
+ // словами. Имена разделов описание повторяет, потому что его читает
18167
+ // модель; сторож копии — `plan-tools.test.ts`, сверка с `SECTIONS` пака.
18168
+ ask: external_exports.looseObject({}).describe(
18169
+ `\u0423\u0437\u043A\u0430\u044F \u043F\u0440\u043E\u0441\u044C\u0431\u0430 \u0440\u0430\u0437\u0434\u0435\u043B\u0430\u043C\u0438 \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 video: ${ASK_SECTIONS.join(", ")}; tempo \u2014 \u0432\u043E \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0440\u0430\u0437 \u0443\u0441\u043A\u043E\u0440\u0438\u0442\u044C, \u0447\u0438\u0441\u043B\u043E \u043E\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430. \u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 true \u2014 \xAB\u043A\u0430\u043A \u0440\u0435\u0448\u0438\u0442 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\xBB. {} \u2014 \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A \u043A\u0430\u043A \u0441\u043D\u044F\u0442 (\u0446\u0432\u0435\u0442, \u0440\u0430\u0441\u043A\u043B\u0430\u0434\u043A\u0430); {"speech": {}} \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u043B\u043E\u0432\u0430 \u0441 \u043D\u043E\u043C\u0435\u0440\u0430\u043C\u0438 (fix_word, \u043A\u0443\u0441\u043E\u043A \u0441\u043E \u0441\u043B\u043E\u0432\u0430). ${FULL_WORDS} \u2014 \u043F\u043E\u043B\u043D\u044B\u0439 \u043C\u043E\u043D\u0442\u0430\u0436, \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 \xAB\u0441\u043C\u043E\u043D\u0442\u0438\u0440\u0443\u0439\xBB \u0446\u0435\u043B\u0438\u043A\u043E\u043C. \u0421 video \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D: \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u2014 \u043E\u0442\u043A\u0430\u0437`
17249
18170
  ).optional(),
17250
18171
  also: external_exports.array(external_exports.string()).describe(
17251
18172
  "\u0427\u0442\u043E \u0435\u0449\u0451 \u0438\u0434\u0451\u0442 \u0432 \u042D\u0422\u041E\u0422 \u0440\u043E\u043B\u0438\u043A: \u0437\u0430\u043F\u0438\u0441\u044C \u044D\u043A\u0440\u0430\u043D\u0430, \u043C\u0443\u0437\u044B\u043A\u0430, \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0438. \u0420\u043E\u043B\u0438 \u0440\u0435\u0448\u0430\u0435\u0442 \u0437\u0430\u043C\u0435\u0440, \u0430 \u043D\u0435 \u0438\u043C\u044F \u0444\u0430\u0439\u043B\u0430. \u0422\u043E\u043B\u044C\u043A\u043E \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 video \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u0438 \u0437\u0430\u0432\u0435\u0434\u0435\u043D\u0438\u0438 \u0440\u043E\u043B\u0438\u043A\u0430"
@@ -17259,15 +18180,12 @@ function planTool(engine, key) {
17259
18180
  style: external_exports.string().describe(
17260
18181
  "\u041F\u0430\u043F\u043A\u0430 \u043F\u0430\u043A\u0430 \u0444\u0438\u0440\u043C\u0435\u043D\u043D\u043E\u0433\u043E \u0441\u0442\u0438\u043B\u044F: \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0442\u0430\u043A, \u043A\u0430\u043A \u0435\u0433\u043E \u043F\u043E\u0442\u043E\u043C \u0441\u043E\u0431\u0435\u0440\u0443\u0442"
17261
18182
  ).optional(),
17262
- // ⚠ ТАБЛИЦЫ ВКУСА НАЗВАНЫ ПРИМЕРОМ НАРОЧНО. Дверь к ним открыта движком
17263
- // (`engine/catalog/handbook.py`, корень `taste`), но модель о них не узнает ниоткуда:
17264
- // черновик называет их не всегда, а решение владельца 2026-09-18 разрешило
17265
- // ей писать план ролика целиком — и числа для него она обязана брать из
17266
- // наших замеров, а не из головы. Про «по одной-две за вызов» сказано тут
17267
- // же: таблицы тяжёлые, а ответ движка обрезан 48 КБ, и вызов на шесть
17268
- // таблиц вернулся бы частями, то есть лишними ходами.
18183
+ // ⚠ ПУТИ — КАК ИХ ПЕЧАТАЕТ ОГЛАВЛЕНИЕ СПРАВОЧНИКА (пакет Б1.14): без
18184
+ // приставки — страница `handbook/`, `taste/…` — таблица вкуса, `vendor/…` —
18185
+ // чужой файл. Заслоны чтения живут в движке (`engine/handbook/reader.py`).
18186
+ // Таблицы вкуса тяжёлые, а ответ движка обрезан 48 КБ: по одной-две.
17269
18187
  read: external_exports.array(external_exports.string()).describe(
17270
- `\u0424\u0430\u0439\u043B\u044B \u0441\u043A\u0438\u043B\u043B\u043E\u0432, \u0437\u043D\u0430\u043D\u0438\u0439 \u0438 \u0422\u0410\u0411\u041B\u0418\u0426 \u0412\u041A\u0423\u0421\u0410, \u043D\u0430\u0437\u0432\u0430\u043D\u043D\u044B\u0435 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u043E\u043C: \xABdirector/SKILL.md\xBB, \xABvendor/taste-skill/SKILL.md\xBB, \xABtaste/rhythm.json\xBB. \u0414\u043E ${FILES_PER_READ} \u0437\u0430 \u0432\u044B\u0437\u043E\u0432, \u043F\u0443\u0442\u0438 \u043E\u0442 \u043F\u0430\u043F\u043A\u0438 \u0441\u043A\u0438\u043B\u043B\u043E\u0432 \u043F\u0430\u043A\u0430 \u2014 \u043A\u0430\u043A \u0438\u0445 \u043D\u0430\u043F\u0435\u0447\u0430\u0442\u0430\u043B \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A, \u0431\u0435\u0437 \xAB../\xBB \u0438 \u0431\u0435\u0437 \u0441\u0432\u043E\u0438\u0445 \u043F\u0443\u0442\u0435\u0439. \u0422\u0430\u0431\u043B\u0438\u0446\u044B \u0432\u043A\u0443\u0441\u0430 \u0442\u044F\u0436\u0451\u043B\u044B\u0435: \u0431\u0435\u0440\u0438 \u0438\u0445 \u043F\u043E \u043E\u0434\u043D\u043E\u0439-\u0434\u0432\u0435 \u0437\u0430 \u0432\u044B\u0437\u043E\u0432, \u0438\u043D\u0430\u0447\u0435 \u043E\u0442\u0432\u0435\u0442 \u043F\u0440\u0438\u0434\u0451\u0442 \u0447\u0430\u0441\u0442\u044F\u043C\u0438`
18188
+ `\u041F\u0443\u0442\u0438 \u043A\u0430\u043A \u0438\u0445 \u043D\u0430\u043F\u0435\u0447\u0430\u0442\u0430\u043B\u0438 \u043E\u0433\u043B\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0441\u043F\u0440\u0430\u0432\u043E\u0447\u043D\u0438\u043A\u0430, \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0438\u043B\u0438 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430: \xABdocument/clips.md\xBB, \xABtaste/rhythm.json\xBB, \xABvendor/taste-skill/SKILL.md\xBB. \u0414\u043E ${FILES_PER_READ} \u0437\u0430 \u0432\u044B\u0437\u043E\u0432, \u0431\u0435\u0437 \xAB../\xBB \u0438 \u0431\u0435\u0437 \u0441\u0432\u043E\u0438\u0445 \u043F\u0443\u0442\u0435\u0439. \u0422\u0430\u0431\u043B\u0438\u0446\u044B \u0432\u043A\u0443\u0441\u0430 \u0442\u044F\u0436\u0451\u043B\u044B\u0435: \u043F\u043E \u043E\u0434\u043D\u043E\u0439-\u0434\u0432\u0435 \u0437\u0430 \u0432\u044B\u0437\u043E\u0432, \u0438\u043D\u0430\u0447\u0435 \u043E\u0442\u0432\u0435\u0442 \u043F\u0440\u0438\u0434\u0451\u0442 \u0447\u0430\u0441\u0442\u044F\u043C\u0438`
17271
18189
  ).optional(),
17272
18190
  part: external_exports.number().describe(
17273
18191
  "\u041A\u0430\u043A\u0430\u044F \u0447\u0430\u0441\u0442\u044C \u043E\u0442\u0432\u0435\u0442\u0430 \u043D\u0443\u0436\u043D\u0430, \u0435\u0441\u043B\u0438 \u0444\u0430\u0439\u043B \u0434\u043B\u0438\u043D\u043D\u044B\u0439: \u0441\u0447\u0451\u0442 \u0441 \u0435\u0434\u0438\u043D\u0438\u0446\u044B. \u0421\u043A\u043E\u043B\u044C\u043A\u043E \u0438\u0445 \u0432\u0441\u0435\u0433\u043E, \u0441\u043A\u0430\u0436\u0435\u0442 \u0441\u0430\u043C \u043E\u0442\u0432\u0435\u0442"
@@ -17296,7 +18214,13 @@ function planTool(engine, key) {
17296
18214
  if (asked.part !== void 0 && asked.read === void 0) {
17297
18215
  say("\xABpart\xBB \u2014 \u044D\u0442\u043E \u0447\u0430\u0441\u0442\u044C \u043E\u0442\u0432\u0435\u0442\u0430 \u0447\u0442\u0435\u043D\u0438\u044F: \u043E\u043D \u0431\u044B\u0432\u0430\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \xABread\xBB", "part");
17298
18216
  }
17299
- for (const key2 of ["also", "together"]) {
18217
+ if (asked.video !== void 0 && asked.ask === void 0) {
18218
+ say(
18219
+ `\u041D\u0430\u0437\u043E\u0432\u0438 \u043F\u0440\u043E\u0441\u044C\u0431\u0443: ask \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E, \u0447\u0442\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u043B ({"tempo": <\u0432\u043E \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0440\u0430\u0437 \u043E\u043D \u0441\u043A\u0430\u0437\u0430\u043B>}, {"pauses": {}}, {"subtitles": {}}); \u0446\u0432\u0435\u0442, \u0440\u0430\u0441\u043A\u043B\u0430\u0434\u043A\u0430, split-screen \u2014 {}; \u0438\u0441\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0441\u043B\u043E\u0432\u043E \u2014 {"speech": {}}. \xAB\u0421\u043C\u043E\u043D\u0442\u0438\u0440\u0443\u0439\xBB \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u2014 ${FULL_WORDS}`,
18220
+ "ask"
18221
+ );
18222
+ }
18223
+ for (const key2 of ["also", "together", "ask"]) {
17300
18224
  if (asked[key2] !== void 0 && asked.video === void 0) {
17301
18225
  say(`\xAB${key2}\xBB \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0441\u044F \u043A \u0437\u0430\u0432\u0435\u0434\u0435\u043D\u0438\u044E \u0440\u043E\u043B\u0438\u043A\u0430 \u0438 \u0431\u044B\u0432\u0430\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \xABvideo\xBB`, key2);
17302
18226
  }
@@ -17306,7 +18230,7 @@ function planTool(engine, key) {
17306
18230
  if (args.read !== void 0) {
17307
18231
  return readSkills(engine, { files: args.read, part: args.part });
17308
18232
  }
17309
- if (args.reel !== void 0) return viewComposition(engine, args.reel);
18233
+ if (args.reel !== void 0) return viewDocument(engine, args.reel);
17310
18234
  if (args.video === void 0) return listReels(engine, args.workspace);
17311
18235
  return planDraft(
17312
18236
  engine,
@@ -17315,7 +18239,8 @@ function planTool(engine, key) {
17315
18239
  also: args.also,
17316
18240
  together: args.together,
17317
18241
  workspace: args.workspace,
17318
- style: args.style
18242
+ style: args.style,
18243
+ ask: args.ask
17319
18244
  },
17320
18245
  key,
17321
18246
  ctx?.emitProgress
@@ -17323,127 +18248,74 @@ function planTool(engine, key) {
17323
18248
  }
17324
18249
  });
17325
18250
  }
17326
- var MOVES = ["fade", "up", "down", "left", "right", "pop", "cut"];
17327
- var DRIFTS = ["none", "up", "down", "left", "right"];
17328
- var SPEEDS = ["slow", "normal", "fast"];
17329
- var SLOTS = ["top", "middle", "bottom"];
18251
+ var FULL_ASK = "all";
18252
+ var FULL_WORDS = `{"${FULL_ASK}": true}`;
18253
+ var ASK_SECTIONS = [
18254
+ "tempo",
18255
+ "pauses",
18256
+ "loudness",
18257
+ "voice",
18258
+ "music",
18259
+ "subtitles",
18260
+ "shots",
18261
+ "world",
18262
+ "inserts",
18263
+ "accents",
18264
+ "intro",
18265
+ "screen",
18266
+ "zoom",
18267
+ "webcam",
18268
+ "regions"
18269
+ ];
18270
+ var OPS = [
18271
+ "set",
18272
+ "unset",
18273
+ "add",
18274
+ "remove",
18275
+ "add_track",
18276
+ "add_group",
18277
+ "fix_word",
18278
+ "add_source",
18279
+ "add_package",
18280
+ "repin",
18281
+ "agree",
18282
+ "remember",
18283
+ "forget"
18284
+ ];
17330
18285
  function opSchema() {
17331
- return external_exports.object({
17332
- op: external_exports.string().describe(
17333
- "hero | remove | add | move | text | accent | word | world | motion | intro | part"
17334
- ),
17335
- from_word: external_exports.number().describe("hero: \u043F\u0435\u0440\u0432\u043E\u0435 \u0441\u043B\u043E\u0432\u043E \u043E\u0442\u0440\u0435\u0437\u043A\u0430").optional(),
17336
- // ⚠ ВТОРОЕ ЧИСЛО — СЛЕДУЮЩЕЕ ЗА ОТРЕЗКОМ, А НЕ ПОСЛЕДНЕЕ В НЁМ. Так их
17337
- // считает план (`lib/draft/build.py`: время конца берётся у слова `to_word - 1`),
17338
- // так же они печатаются в черновике словами, и так же их ждёт правка.
17339
- // До 2026-09-18 здесь стояло «последнее слово отрезка» — и модель, списав
17340
- // число из черновика, промахивалась на одно слово на каждой границе.
17341
- to_word: external_exports.number().describe(
17342
- "hero: \u0441\u043B\u043E\u0432\u043E \u0421\u041B\u0415\u0414\u0423\u042E\u0429\u0415\u0415 \u0437\u0430 \u043E\u0442\u0440\u0435\u0437\u043A\u043E\u043C \u2014 \u043E\u043D\u043E \u0441\u0430\u043C\u043E \u0432 \u043E\u0442\u0440\u0435\u0437\u043E\u043A \u043D\u0435 \u0432\u0445\u043E\u0434\u0438\u0442. \u0420\u043E\u0432\u043D\u043E \u0442\u043E \u0447\u0438\u0441\u043B\u043E, \u0447\u0442\u043E \u043D\u0430\u043F\u0435\u0447\u0430\u0442\u0430\u043D\u043E \u0432 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0435"
17343
- ).optional(),
17344
- at_word: external_exports.number().describe("add, move: \u043D\u0430 \u043A\u0430\u043A\u043E\u043C \u0441\u043B\u043E\u0432\u0435").optional(),
17345
- until_word: external_exports.number().describe(
17346
- "add: \u0441\u043B\u043E\u0432\u043E \u0421\u041B\u0415\u0414\u0423\u042E\u0429\u0415\u0415 \u0437\u0430 \u043E\u0442\u0440\u0435\u0437\u043A\u043E\u043C \u2014 \u043E\u043D\u043E \u0441\u0430\u043C\u043E \u043D\u0435 \u0432\u0445\u043E\u0434\u0438\u0442"
17347
- ).optional(),
17348
- word: external_exports.number().describe("accent, word: \u043D\u043E\u043C\u0435\u0440 \u0441\u043B\u043E\u0432\u0430").optional(),
17349
- insert: external_exports.string().describe(
17350
- "remove, move, text, motion: \u0438\u043C\u044F \u0432\u0441\u0442\u0430\u0432\u043A\u0438 \u0438\u0437 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0430, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 chips-1"
17351
- ).optional(),
17352
- item: external_exports.number().describe("text: \u043D\u043E\u043C\u0435\u0440 \u043F\u0443\u043D\u043A\u0442\u0430 \u0441\u043F\u0438\u0441\u043A\u0430, \u0441 \u0435\u0434\u0438\u043D\u0438\u0446\u044B").optional(),
17353
- template: external_exports.string().describe("add: \u0448\u0430\u0431\u043B\u043E\u043D \u0432\u0441\u0442\u0430\u0432\u043A\u0438").optional(),
17354
- text: external_exports.string().describe(
17355
- "add: \u043F\u043E\u0434\u043F\u0438\u0441\u044C \u043D\u043E\u0432\u043E\u0439 \u0432\u0441\u0442\u0430\u0432\u043A\u0438; intro: \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0438 \u2014 \u0443 \u043D\u0435\u0451 \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E"
17356
- ).optional(),
17357
- // ⚠ СЕКУНДА САМОЙ ЗАПИСИ, А НЕ СЕКУНДА РОЛИКА, И ПОЛЕ У НЕЁ СВОЁ. Окон
17358
- // записи экрана в плане бывает несколько, и каждое следующее продолжает
17359
- // предыдущее само (решение владельца 2026-09-18). Поле нужно там, где
17360
- // продолжение не годится: «а тут покажи с середины». Имя то же, что у
17361
- // слоя в плане наложений, — это одна и та же мысль, и второго имени для
17362
- // неё быть не должно.
17363
- file_start: external_exports.number().describe(
17364
- "add: \u0441 \u043A\u0430\u043A\u043E\u0439 \u0441\u0435\u043A\u0443\u043D\u0434\u044B \u0421\u0410\u041C\u041E\u0419 \u0437\u0430\u043F\u0438\u0441\u0438 \u044D\u043A\u0440\u0430\u043D\u0430 \u0438\u0433\u0440\u0430\u0442\u044C \u044D\u0442\u043E \u043E\u043A\u043D\u043E. \u041D\u0435 \u043F\u0438\u0448\u0435\u0448\u044C \u2014 \u043E\u043A\u043D\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442 \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0435\u0435 \u0441 \u0442\u043E\u0433\u043E \u043C\u0435\u0441\u0442\u0430, \u0433\u0434\u0435 \u043E\u043D\u043E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043B\u043E\u0441\u044C"
17365
- ).optional(),
17366
- value: external_exports.string().describe(
17367
- "hero: full|window|close-up|none; accent: accent|normal; text: \u043F\u043E\u0434\u043F\u0438\u0441\u044C; word: \u043A\u0430\u043A \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u043E; world: grid|file|none; part: on|off"
17368
- ).optional(),
17369
- // ⚠ У ЭТИХ ДВУХ СТРОК ПЛАНА НЕТ ИМЕНИ ВСТАВКИ, ПОЭТОМУ И ПОЯВИЛОСЬ ОТДЕЛЬНОЕ
17370
- // ПОЛЕ. Вставку человек убирает по её имени (`remove`, `chips-1`), а
17371
- // субтитры и звуковые удары — это сами себе строка плана, целиком
17372
- // (`engine/brief.py`, `PARTS`).
17373
- what: external_exports.string().describe(
17374
- "part: \u043A\u0430\u043A\u0443\u044E \u0441\u0442\u0440\u043E\u043A\u0443 \u043F\u043B\u0430\u043D\u0430 \u0432\u044B\u0447\u0435\u0440\u043A\u043D\u0443\u0442\u044C \u0438\u043B\u0438 \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u2014 subtitles | sfx"
17375
- ).optional(),
17376
- // Движение вставки словами: канвы модель не знает, а движок знает — в
17377
- // пиксели и секунды слова переводит шаг вставок по таблицам вкуса.
17378
- enter: external_exports.string().describe(
17379
- `motion, intro: \u043A\u0430\u043A \u0432\u0441\u0442\u0430\u0432\u043A\u0430 \u0438\u043B\u0438 \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u043F\u0440\u0438\u0445\u043E\u0434\u0438\u0442 \u2014 ${MOVES.join(" | ")}. fade \u2014 \u043F\u0440\u043E\u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F; up/down/left/right \u2014 \u0432\u044A\u0435\u0437\u0436\u0430\u0435\u0442 \u0441 \u044D\u0442\u043E\u0439 \u0441\u0442\u043E\u0440\u043E\u043D\u044B; pop \u2014 \u0432\u044B\u0441\u043A\u0430\u043A\u0438\u0432\u0430\u0435\u0442; cut \u2014 \u0440\u0435\u0437\u043E\u043C`
17380
- ).optional(),
17381
- exit: external_exports.string().describe(
17382
- `motion, intro: \u043A\u0430\u043A \u0443\u0445\u043E\u0434\u0438\u0442 \u2014 ${MOVES.join(" | ")}. \u0421\u0442\u043E\u0440\u043E\u043D\u0430 \u0442\u0443\u0442 \u0437\u043D\u0430\u0447\u0438\u0442, \u041A\u0423\u0414\u0410 \u0443\u0435\u0437\u0436\u0430\u0435\u0442: left \u2014 \u0432\u043B\u0435\u0432\u043E`
17383
- ).optional(),
17384
- drift: external_exports.string().describe(
17385
- `motion, intro: \u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E\u0435 \u0441\u043C\u0435\u0449\u0435\u043D\u0438\u0435 \u0437\u0430 \u0436\u0438\u0437\u043D\u044C \u0432\u0441\u0442\u0430\u0432\u043A\u0438 \u2014 ${DRIFTS.join(" | ")}; none \u2014 \u0441\u0442\u043E\u0438\u0442 \u043D\u0430 \u043C\u0435\u0441\u0442\u0435`
17386
- ).optional(),
17387
- speed: external_exports.string().describe(
17388
- `motion, intro: \u0441\u043A\u043E\u0440\u043E\u0441\u0442\u044C \u0432\u0445\u043E\u0434\u0430 \u0438 \u0443\u0445\u043E\u0434\u0430 \u2014 ${SPEEDS.join(" | ")}. normal \u2014 \u043A\u0430\u043A \u0437\u0430\u043C\u0435\u0440\u0435\u043D\u043E`
17389
- ).optional(),
17390
- slot: external_exports.string().describe(
17391
- `motion, intro: \u0433\u0434\u0435 \u0432\u0441\u0442\u0430\u0432\u043A\u0430 \u0441\u0442\u043E\u0438\u0442 \u043F\u043E \u0432\u0435\u0440\u0442\u0438\u043A\u0430\u043B\u0438 \u0432\u043D\u0443\u0442\u0440\u0438 \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0439 \u0440\u0430\u043C\u043A\u0438 \u2014 ${SLOTS.join(" | ")}`
17392
- ).optional(),
17393
- // Карточка в начале ролика: её вторая строка, длительность и снятие.
17394
- // ⚠ «text» СЛУЖИТ ДВУМ ОПЕРАЦИЯМ, и это не путаница: у `add` это подпись
17395
- // новой вставки, у `intro` — заголовок карточки. Развести их вторым именем
17396
- // значило бы завести модели два слова на одно и то же — «что написать».
17397
- note: external_exports.string().describe(
17398
- "intro: \u0432\u0442\u043E\u0440\u0430\u044F \u0441\u0442\u0440\u043E\u043A\u0430 \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0438, \u043F\u043E\u043C\u0435\u043B\u044C\u0447\u0435 \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u0430"
17399
- ).optional(),
17400
- // ⚠ ЧИСЛО УМОЛЧАНИЯ ЗДЕСЬ НЕ НАЗЫВАЕТСЯ НАРОЧНО. Его дом один — «params»
17401
- // паспорта шага (`steps/intro/step.json`), и покупатель вправе поправить
17402
- // его в своей копии шага. Назови мы семёрку здесь — модель обещала бы её
17403
- // тому, у кого стоит другое число.
17404
- seconds: external_exports.number().describe(
17405
- "intro: \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0435\u043A\u0443\u043D\u0434 \u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044F \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0430. \u041D\u0435 \u043D\u0430\u0437\u0432\u0430\u043B \u2014 \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u0435 \u0448\u0430\u0433\u0430"
17406
- ).optional(),
17407
- off: external_exports.boolean().describe("intro: \u0443\u0431\u0440\u0430\u0442\u044C \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0443 \u0438\u0437 \u043D\u0430\u0447\u0430\u043B\u0430 \u0440\u043E\u043B\u0438\u043A\u0430").optional()
18286
+ return external_exports.looseObject({
18287
+ op: external_exports.string().describe(OPS.join(" | "))
17408
18288
  });
17409
18289
  }
17410
18290
  function planEditTool(engine, key) {
17411
18291
  return defineTool({
17412
18292
  name: "plan_edit",
17413
- title: "\u041F\u043E\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043F\u043B\u0430\u043D \u0440\u043E\u043B\u0438\u043A\u0430",
17414
- description: "\u041F\u0440\u0430\u0432\u043A\u0430 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0430 \u0438 \u0443\u0436\u0435 \u0441\u043E\u0431\u0440\u0430\u043D\u043D\u043E\u0433\u043E: \xAB\u043F\u043E\u043A\u0430\u0436\u0438 \u043C\u0435\u043D\u044F \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u0432\u043E\u0442 \u0442\u0443\u0442\xBB, \xAB\u0443\u0431\u0435\u0440\u0438 \u0441\u043F\u0438\u0441\u043E\u043A\xBB, \xAB\u0432 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u0435 \u043D\u0435 \u0442\u043E \u0441\u043B\u043E\u0432\u043E\xBB, \xAB\u043F\u0443\u0441\u0442\u044C \u0432\u044B\u0435\u0437\u0436\u0430\u0435\u0442 \u0441\u043B\u0435\u0432\u0430\xBB, \xAB\u043F\u043E\u0434\u043F\u0438\u0448\u0438 \u0442\u0440\u0435\u0442\u0438\u0439 \u043F\u0443\u043D\u043A\u0442\xBB. \u041E\u0434\u043D\u0430 \u043F\u0440\u043E\u0441\u044C\u0431\u0430 \u2014 \u043E\u0434\u043D\u0430 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044F; \u0441 rebuild \u0441\u0440\u0430\u0437\u0443 \u043F\u0435\u0440\u0435\u0441\u043E\u0431\u0438\u0440\u0430\u0435\u0442, \u0438 \u044D\u0442\u043E \u0441\u0442\u043E\u0438\u0442 \u0441\u0435\u043A\u0443\u043D\u0434, \u0430 \u043D\u0435 \u043C\u0438\u043D\u0443\u0442: \u0437\u0430\u043D\u043E\u0432\u043E \u0434\u0435\u043B\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u0440\u043E\u043D\u0443\u0442\u043E\u0435.\n\u041E\u043F\u0435\u0440\u0430\u0446\u0438\u0438: hero (\u043A\u0440\u0443\u043F\u043D\u043E\u0441\u0442\u044C \u043D\u0430 \u043E\u0442\u0440\u0435\u0437\u043A\u0435 \u0441\u043B\u043E\u0432), remove, add, move, text (\u043F\u043E\u0434\u043F\u0438\u0441\u044C \u0432\u0441\u0442\u0430\u0432\u043A\u0438 \u0438\u043B\u0438 \u0435\u0451 \u043F\u0443\u043D\u043A\u0442\u0430), accent (\u0441\u043B\u043E\u0432\u043E \u043A\u0440\u0443\u043F\u043D\u0435\u0435 \u0432 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u0430\u0445), word (\u0438\u0441\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u043D\u043D\u043E\u0435 \u0441\u043B\u043E\u0432\u043E), world (\u0444\u043E\u043D \u0437\u0430 \u0433\u0435\u0440\u043E\u0435\u043C), motion (\u043A\u0430\u043A \u0432\u0441\u0442\u0430\u0432\u043A\u0430 \u043F\u0440\u0438\u0445\u043E\u0434\u0438\u0442, \u0443\u0445\u043E\u0434\u0438\u0442 \u0438 \u0433\u0434\u0435 \u0441\u0442\u043E\u0438\u0442), intro (\u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u0432 \u043D\u0430\u0447\u0430\u043B\u0435 \u0440\u043E\u043B\u0438\u043A\u0430: text, note, seconds; \u0443\u0431\u0440\u0430\u0442\u044C \u2014 off; \u0441\u043B\u043E\u0432\u0430 \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044F \u0443 \u043D\u0435\u0451 \u0442\u0435 \u0436\u0435 \u2014 enter, exit, drift, speed, slot), part (\u0432\u044B\u0447\u0435\u0440\u043A\u043D\u0443\u0442\u044C \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u0441\u0442\u0440\u043E\u043A\u0443 \u043F\u043B\u0430\u043D\u0430, \u0443 \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u043D\u0435\u0442 \u0438\u043C\u0435\u043D\u0438 \u0432\u0441\u0442\u0430\u0432\u043A\u0438: what=subtitles|sfx, value=on|off \u2014 \xAB\u0431\u0435\u0437 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u043E\u0432\xBB, \xAB\u0431\u0435\u0437 \u0437\u0432\u0443\u043A\u043E\u0432\u044B\u0445 \u0443\u0434\u0430\u0440\u043E\u0432\xBB).\nintro \u2014 \u0435\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u0430\u044F \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044F, \u043A\u043E\u0442\u043E\u0440\u0430\u044F \u041C\u0415\u041D\u042F\u0415\u0422 \u0414\u041B\u0418\u041D\u0423 \u0440\u043E\u043B\u0438\u043A\u0430: \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u0432\u0441\u0442\u0430\u0451\u0442 \u041F\u0415\u0420\u0415\u0414 \u043D\u0438\u043C, \u0430 \u0441\u0430\u043C \u0440\u043E\u043B\u0438\u043A \u043E\u0441\u0442\u0430\u0451\u0442\u0441\u044F \u043A\u0430\u0434\u0440 \u0432 \u043A\u0430\u0434\u0440 \u043F\u0440\u0435\u0436\u043D\u0438\u043C. \u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0438 \u0432 \xAB\u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0430\u0443\u0437\u044B\xBB.\n\u041D\u043E\u043C\u0435\u0440\u0430 \u0441\u043B\u043E\u0432 \u0438 \u0438\u043C\u0435\u043D\u0430 \u0432\u0441\u0442\u0430\u0432\u043E\u043A \u0431\u0435\u0440\u0438 \u0418\u0417 \u0427\u0415\u0420\u041D\u041E\u0412\u0418\u041A\u0410 (plan \u0441 video). \u041D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u0438\u0445: \u043F\u0440\u0430\u0432\u043A\u0430 \u043C\u0438\u043C\u043E \u0446\u0435\u043B\u0438 \u0431\u0443\u0434\u0435\u0442 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u0430 \u0441\u043B\u043E\u0432\u0430\u043C\u0438.\nconfirm \u2014 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0441\u043A\u0430\u0437\u0430\u043B \xAB\u0434\u0430\xBB \u043D\u0430 \u043D\u044B\u043D\u0435\u0448\u043D\u044E\u044E \u0440\u0435\u0434\u0430\u043A\u0446\u0438\u044E; \u0431\u0435\u0437 \u044D\u0442\u043E\u0433\u043E \u0441\u0431\u043E\u0440\u043A\u0430 \u043F\u043B\u0430\u043D \u043D\u0435 \u0432\u043E\u0437\u044C\u043C\u0451\u0442. \u041B\u042E\u0411\u0410\u042F \u043F\u0440\u0430\u0432\u043A\u0430 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435 \u0441\u043D\u0438\u043C\u0430\u0435\u0442, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 confirm \u0448\u043B\u0451\u0442\u0441\u044F \u0442\u0435\u043C \u0436\u0435 \u0432\u044B\u0437\u043E\u0432\u043E\u043C, \u0447\u0442\u043E \u0438 \u043F\u0440\u0430\u0432\u043A\u0430, \u2014 \u043F\u043E\u0441\u043B\u0435 \u0442\u043E\u0433\u043E \u043A\u0430\u043A \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0443\u0441\u043B\u044B\u0448\u0430\u043B, \u0447\u0442\u043E \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u043E\u0441\u044C.\nplan \u2014 \u0432\u0435\u0441\u044C \u043F\u043B\u0430\u043D \u0440\u043E\u043B\u0438\u043A\u0430 \u0446\u0435\u043B\u0438\u043A\u043E\u043C, \u043D\u0430\u043F\u0438\u0441\u0430\u043D\u043D\u044B\u0439 \u0442\u043E\u0431\u043E\u0439: \u0434\u0432\u0438\u0436\u043E\u043A \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442 \u0435\u0433\u043E \u043F\u043E \u0441\u0445\u0435\u043C\u0435 \u0438 \u043F\u043E \u043F\u043E\u0440\u043E\u0433\u0430\u043C \u0432\u043A\u0443\u0441\u0430 \u0438 \u043F\u0440\u0438 \u043D\u0430\u0440\u0443\u0448\u0435\u043D\u0438\u0438 \u043E\u0442\u043A\u0430\u0436\u0435\u0442 \u0441\u043B\u043E\u0432\u0430\u043C\u0438. \u0422\u0430\u043A \u0434\u0435\u043B\u0430\u044E\u0442, \u043A\u043E\u0433\u0434\u0430 \u043F\u0440\u0430\u0432\u043A\u0430\u043C\u0438 \u0443\u0436\u0435 \u043D\u0435 \u043E\u0431\u043E\u0439\u0442\u0438\u0441\u044C. draft \u2014 \u043E\u0431\u0440\u0430\u0442\u043D\u043E\u0435: \u0437\u0430\u0431\u044B\u0442\u044C \u0442\u0432\u043E\u0439 \u043F\u043B\u0430\u043D \u0438 \u0432\u0435\u0440\u043D\u0443\u0442\u044C\u0441\u044F \u043A \u043D\u0430\u0448\u0435\u043C\u0443 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0443. \u041F\u0440\u043E\u0441\u044C\u0431\u0430 \u0443\u0437\u043A\u0430\u044F (\xAB\u043F\u0440\u043E\u0441\u0442\u043E \u0443\u0441\u043A\u043E\u0440\u044C\xBB) \u2014 \u043F\u043B\u0430\u043D\u0430 \u043D\u0435 \u043F\u0438\u0448\u0438 \u0432\u043E\u0432\u0441\u0435, \u0438\u043D\u0430\u0447\u0435 \u0432 \u0440\u043E\u043B\u0438\u043A\u0435 \u043F\u043E\u044F\u0432\u0438\u0442\u0441\u044F \u0442\u043E, \u043E \u0447\u0451\u043C \u043D\u0438\u043A\u0442\u043E \u043D\u0435 \u043F\u0440\u043E\u0441\u0438\u043B.\nproject \u2014 \u0432\u0435\u0441\u044C \u043E\u0431\u0449\u0438\u0439 naparnik-project/1 \u0446\u0435\u043B\u0438\u043A\u043E\u043C. \u0414\u0432\u0438\u0436\u043E\u043A \u0441\u0430\u043C \u0438\u0437\u043C\u0435\u0440\u044F\u0435\u0442 \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A\u0438 \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442; \u043F\u043E\u0441\u043B\u0435 \u043F\u043E\u043A\u0430\u0437\u0430 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 confirm \u043F\u0440\u0438\u0432\u044F\u0437\u044B\u0432\u0430\u0435\u0442 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435 \u043A \u0442\u043E\u0447\u043D\u043E\u0439 \u0440\u0435\u0434\u0430\u043A\u0446\u0438\u0438 \u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044F\u043C.\n\u0421\u043E step \u043F\u0440\u0430\u0432\u0438\u0442 \u043D\u0435 \u043F\u043B\u0430\u043D, \u0430 \u041F\u041E\u0420\u042F\u0414\u041E\u041A \u0428\u0410\u0413\u041E\u0412 \u043C\u043E\u043D\u0442\u0430\u0436\u0430: show \u2014 \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0439, mine \u2014 \u0437\u0430\u0432\u0435\u0441\u0442\u0438 \u0441\u0432\u043E\u044E \u043A\u043E\u043F\u0438\u044E \u043D\u0430\u0448\u0435\u0433\u043E, insert \u2014 \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0441\u0432\u043E\u0439 \u0448\u0430\u0433 \u0434\u043E \u0438\u043B\u0438 \u043F\u043E\u0441\u043B\u0435 \u044D\u0442\u0430\u043F\u0430, replace \u2014 \u043F\u043E\u0434\u043C\u0435\u043D\u0438\u0442\u044C \u0448\u0430\u0433 \u0443 \u044D\u0442\u0430\u043F\u0430, remove \u2014 \u0443\u0431\u0440\u0430\u0442\u044C \u044D\u0442\u0430\u043F. \u041F\u0438\u0448\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u043F\u0430\u043F\u043A\u0443 mine \u0434\u043E\u043C\u0430 \u041D\u0430\u043F\u0430\u0440\u043D\u0438\u043A\u0430: \u043D\u0430\u0448 \u043F\u043E\u0440\u044F\u0434\u043E\u043A \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0435\u0442\u0441\u044F \u043D\u0438\u043A\u043E\u0433\u0434\u0430, \u0438 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u0434\u0432\u0438\u0436\u043A\u0430 \u0442\u0432\u043E\u044E \u043A\u043E\u043F\u0438\u044E \u043D\u0435 \u0437\u0430\u043C\u0435\u043D\u044F\u0435\u0442. \u0421\u0432\u043E\u0439 \u0448\u0430\u0433 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u0432\u0441\u0442\u0430\u0432\u043A\u0435 \u2014 \u0447\u0442\u043E \u0432 \u043D\u0451\u043C \u0447\u0438\u043D\u0438\u0442\u044C, \u0441\u043A\u0430\u0436\u0443\u0442 \u0441\u043B\u043E\u0432\u0430\u043C\u0438.",
18293
+ title: "\u041F\u043E\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0440\u043E\u043B\u0438\u043A\u0430",
18294
+ description: `\u041F\u0440\u0430\u0432\u043A\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u0440\u043E\u043B\u0438\u043A\u0430 reel.json \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044F\u043C\u0438 \u043F\u043E id \u2014 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u043D\u0435 \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0442\u0441\u044F. \u041E\u0434\u043D\u0430 \u043F\u0440\u043E\u0441\u044C\u0431\u0430 \u2014 \u043E\u0434\u043D\u0430 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044F: \xAB\u0437\u0432\u0443\u043A\u0438 \u0433\u0440\u043E\u043C\u0447\u0435\xBB \u2014 \u043E\u0434\u043D\u0430 \u0441\u0442\u0440\u043E\u043A\u0430 {"op": "set", "target": "defaults", "path": "sound.gain", "value": 7}. \u041F\u0430\u0447\u043A\u0430 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0435\u0442\u0441\u044F \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u0438\u043B\u0438 \u043D\u0438\u043A\u0430\u043A. \u041E\u043F\u0435\u0440\u0430\u0446\u0438\u0438 \u2014 document/edits.md \u0441\u043F\u0440\u0430\u0432\u043E\u0447\u043D\u0438\u043A\u0430: ${OPS.join(", ")}.
18295
+ stamps \u2014 \u043F\u0435\u0447\u0430\u0442\u0438 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u0445 \u0441\u0442\u0440\u0430\u043D\u0438\u0446 (\xABdocument/clips.md@1a2b3c4d\xBB \u2014 \u043F\u0435\u0440\u0432\u0430\u044F \u0441\u0442\u0440\u043E\u043A\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 plan \u0441 read). \u041F\u0440\u0430\u0432\u043A\u0430 \u043A\u043B\u0438\u043F\u043E\u0432, \u043A\u043E\u0440\u043D\u044F, \u043E\u0431\u0449\u0435\u0433\u043E, \u0441\u0432\u043E\u0435\u0433\u043E \u043F\u0430\u043A\u0435\u0442\u0430 \u0438\u043B\u0438 \u0448\u0430\u0433\u0430 \u0431\u0435\u0437 \u043F\u0435\u0447\u0430\u0442\u0438 \u043E\u0442\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u0438 \u043F\u0440\u0438\u043D\u043E\u0441\u0438\u0442 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443 \u0446\u0435\u043B\u0438\u043A\u043E\u043C: \u043F\u0440\u043E\u0447\u0442\u0438 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u0432\u044B\u0437\u043E\u0432.
18296
+ \xAB\u0417\u0430\u043F\u043E\u043C\u043D\u0438\xBB, \xAB\u0432\u0441\u0435\u0433\u0434\u0430 \u0442\u0430\u043A\xBB \u2014 remember (\u0441\u0432\u043E\u0451 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430, work/remember.md): \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u043D\u0435 \u043C\u0435\u043D\u044F\u0435\u0442, \u043B\u043E\u0436\u0438\u0442\u0441\u044F \u0432 \u043A\u0430\u0436\u0434\u044B\u0439 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A; \u043F\u0435\u0447\u0430\u0442\u0438 \u043D\u0435 \u043D\u0443\u0436\u043D\u043E.
18297
+ \xAB\u0414\u0430\xBB \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u2014 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044F {"op": "agree"}: \u0442\u0435\u043C \u0436\u0435 \u0432\u044B\u0437\u043E\u0432\u043E\u043C, \u0447\u0442\u043E \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043F\u0440\u0430\u0432\u043A\u0430, \u0438\u043B\u0438 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E; \u043F\u0435\u0447\u0430\u0442\u0438 \u0435\u0439 \u043D\u0435 \u043D\u0443\u0436\u043D\u043E. \u041B\u044E\u0431\u0430\u044F \u043F\u0440\u0430\u0432\u043A\u0430 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435 \u0441\u043D\u0438\u043C\u0430\u0435\u0442. \u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u043F\u0440\u0430\u0432\u0438\u0442\u0441\u044F \u0422\u041E\u041B\u042C\u041A\u041E \u0437\u0434\u0435\u0441\u044C: \u0444\u0430\u0439\u043B, \u043F\u043E\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043D\u044B\u0439 \u043C\u0438\u043C\u043E plan_edit, \u043D\u0435 \u0441\u043E\u0433\u043B\u0430\u0441\u0443\u0435\u0442\u0441\u044F \u0438 \u043D\u0435 \u0441\u043E\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044F.
18298
+ rebuild \u2014 \u0441\u0440\u0430\u0437\u0443 \u0441\u043E\u0431\u0440\u0430\u0442\u044C \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442: \u043E\u0442\u0432\u0435\u0447\u0443 \u043D\u043E\u043C\u0435\u0440\u043E\u043C \u0437\u0430\u0434\u0430\u0447\u0438, \u0434\u0430\u043B\u044C\u0448\u0435 wait.
18299
+ \u0421\u043E step \u043F\u0440\u0430\u0432\u0438\u0442 \u043D\u0435 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442, \u0430 \u041F\u041E\u0420\u042F\u0414\u041E\u041A \u0428\u0410\u0413\u041E\u0412 \u0441\u0442\u0430\u0440\u043E\u0433\u043E \u043C\u043E\u043D\u0442\u0430\u0436\u0430: show, mine, insert, replace, remove. render \u043F\u043E \u044D\u0442\u043E\u043C\u0443 \u043F\u043E\u0440\u044F\u0434\u043A\u0443 \u041D\u0415 \u0441\u043E\u0431\u0438\u0440\u0430\u0435\u0442, \u0438 \u0440\u043E\u043B\u0438\u043A \u043E\u0442 step \u043D\u0435 \u043C\u0435\u043D\u044F\u0435\u0442\u0441\u044F. \u0421\u0432\u043E\u0439 \u044D\u0444\u0444\u0435\u043A\u0442 \u0432 \u0440\u043E\u043B\u0438\u043A\u0435 \u2014 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0435\u0439 set \u0441 uses: "mine:<\u0438\u043C\u044F>".`,
17415
18300
  ui: { resourceUri: RUNBAR_RESOURCE_URI },
17416
18301
  args: external_exports.object({
17417
18302
  reel: external_exports.string().describe(
17418
18303
  "\u041F\u0430\u043F\u043A\u0430 \u0440\u043E\u043B\u0438\u043A\u0430 \u0438\u043B\u0438 \u043F\u0443\u0442\u044C \u043A \u0432\u0438\u0434\u0435\u043E\u0444\u0430\u0439\u043B\u0443 \u2014 \u0447\u0442\u043E \u0435\u0441\u0442\u044C \u043F\u043E\u0434 \u0440\u0443\u043A\u043E\u0439"
17419
18304
  ).optional(),
17420
- ops: external_exports.array(opSchema()).describe("\u0421\u043F\u0438\u0441\u043E\u043A \u043F\u0440\u0430\u0432\u043E\u043A; \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u044E\u0442\u0441\u044F \u043F\u043E \u043F\u043E\u0440\u044F\u0434\u043A\u0443").optional(),
17421
- by: external_exports.enum(["model", "human"]).describe(
17422
- "\u041A\u0442\u043E \u0432\u043F\u0438\u0441\u0430\u043B \u043F\u0440\u0430\u0432\u043A\u0443. \xABmodel\xBB \u2014 \u0442\u044B \u0441\u0430\u043C \u0437\u0430\u043A\u0440\u044B\u043B \u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u043B\u043E\u0442 \u043F\u043E\u0434\u043F\u0438\u0441\u0438; \xABhuman\xBB \u2014 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u043B \u0441\u043B\u043E\u0432\u0430\u043C\u0438. \u0423\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u0435 \u2014 model"
17423
- ).optional(),
17424
- rebuild: external_exports.boolean().describe(
17425
- "\u0421\u0440\u0430\u0437\u0443 \u043F\u0435\u0440\u0435\u0441\u043E\u0431\u0440\u0430\u0442\u044C \u0440\u043E\u043B\u0438\u043A. \u041E\u0442\u0432\u0435\u0447\u0443 \u043D\u043E\u043C\u0435\u0440\u043E\u043C \u0437\u0430\u0434\u0430\u0447\u0438, \u0434\u0430\u043B\u044C\u0448\u0435 wait"
17426
- ).optional(),
17427
- confirm: external_exports.boolean().describe(
17428
- "\u0427\u0435\u043B\u043E\u0432\u0435\u043A \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043B \u043D\u044B\u043D\u0435\u0448\u043D\u044E\u044E \u0440\u0435\u0434\u0430\u043A\u0446\u0438\u044E \u043F\u043B\u0430\u043D\u0430. \u041B\u044E\u0431\u0430\u044F \u043F\u0440\u0430\u0432\u043A\u0430 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435 \u0441\u043D\u0438\u043C\u0430\u0435\u0442 \u2014 \u0448\u043B\u0438 \u044D\u0442\u0438\u043C \u0436\u0435 \u0432\u044B\u0437\u043E\u0432\u043E\u043C, \u0447\u0442\u043E \u0438 \u043F\u0440\u0430\u0432\u043A\u0443"
18305
+ ops: external_exports.array(opSchema()).describe(
18306
+ '\u041E\u043F\u0435\u0440\u0430\u0446\u0438\u0438 \u043F\u043E \u043F\u043E\u0440\u044F\u0434\u043A\u0443; \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435 \u2014 {"op": "agree"}'
17429
18307
  ).optional(),
17430
- // ⚠ СХЕМЫ ПЛАНА ЗДЕСЬ НЕТ НАРОЧНО, И ЭТО НЕ ЛЕНЬ. Дом договора
17431
- // `naparnik-plan/1` один — движок (`packs/montage/lib/draft/build.py`), и он же
17432
- // единственный судья: проверяет по схеме и по порогам брака таблиц вкуса.
17433
- // Перепиши мы состав плана сюда — у договора появилась бы вторая правда,
17434
- // которая расходится с первой молча, а узнаёт об этом человек собранным
17435
- // роликом. Поля свободные, а отказ приходит от того, кто план исполняет.
17436
- plan: external_exports.looseObject({}).describe(
17437
- "\u0412\u0415\u0421\u042C \u043F\u043B\u0430\u043D \u0440\u043E\u043B\u0438\u043A\u0430 \u0446\u0435\u043B\u0438\u043A\u043E\u043C, \u043D\u0430\u043F\u0438\u0441\u0430\u043D\u043D\u044B\u0439 \u0442\u043E\u0431\u043E\u0439, \u2014 \u0434\u043E\u0433\u043E\u0432\u043E\u0440 naparnik-plan/1. \u0427\u0438\u0441\u043B\u0430 \u0431\u0435\u0440\u0438 \u0438\u0437 \u0442\u0430\u0431\u043B\u0438\u0446 \u0432\u043A\u0443\u0441\u0430 (plan \u0441 read), \u0430 \u043D\u0435 \u0438\u0437 \u0433\u043E\u043B\u043E\u0432\u044B: \u0434\u0432\u0438\u0436\u043E\u043A \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442 \u043F\u043B\u0430\u043D \u0438 \u043E\u0442\u043A\u0430\u0436\u0435\u0442 \u0441\u043B\u043E\u0432\u0430\u043C\u0438, \u043D\u0430\u0437\u0432\u0430\u0432, \u0447\u0442\u043E \u043D\u0430\u0440\u0443\u0448\u0435\u043D\u043E \u0438 \u043A\u0430\u043A\u0430\u044F \u043D\u043E\u0440\u043C\u0430"
18308
+ stamps: external_exports.array(external_exports.string()).describe(
18309
+ "\u041F\u0435\u0447\u0430\u0442\u0438 \u0441\u0442\u0440\u0430\u043D\u0438\u0446 \u0441\u043F\u0440\u0430\u0432\u043E\u0447\u043D\u0438\u043A\u0430, \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u0445 \u0432 \u044D\u0442\u043E\u043C \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u0435: \xAB\u043F\u0443\u0442\u044C@\u043A\u043E\u0434\xBB"
17438
18310
  ).optional(),
17439
- project: external_exports.looseObject({}).describe(
17440
- "\u0412\u0415\u0421\u042C \u043E\u0431\u0449\u0438\u0439 \u043F\u0440\u043E\u0435\u043A\u0442 \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u0446\u0435\u043B\u0438\u043A\u043E\u043C, \u0434\u043E\u0433\u043E\u0432\u043E\u0440 naparnik-project/1: \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0438, \u0434\u043E\u0440\u043E\u0436\u043A\u0438, \u043A\u043B\u0438\u043F\u044B, \u044D\u0444\u0444\u0435\u043A\u0442\u044B \u0438 \u0432\u044B\u0445\u043E\u0434. \u0414\u0432\u0438\u0436\u043E\u043A \u0438\u0437\u043C\u0435\u0440\u0438\u0442 \u0444\u0430\u0439\u043B\u044B \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0434\u043E \u0437\u0430\u043F\u0438\u0441\u0438. \u041B\u044E\u0431\u0430\u044F \u043D\u043E\u0432\u0430\u044F \u0440\u0435\u0434\u0430\u043A\u0446\u0438\u044F \u0441\u043D\u0438\u043C\u0430\u0435\u0442 \u043F\u0440\u0435\u0436\u043D\u0435\u0435 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435"
18311
+ by: external_exports.enum(["model", "human"]).describe(
18312
+ "\u041A\u0442\u043E \u043F\u0440\u0430\u0432\u0438\u0442 \u0438 \u043A\u0442\u043E \u0441\u043A\u0430\u0437\u0430\u043B \xAB\u0434\u0430\xBB: model \u2014 \u0442\u044B \u0441 \u0435\u0433\u043E \u0441\u043B\u043E\u0432, human \u2014 \u043E\u043D \u0441\u0430\u043C. \u0423\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u0435 \u2014 model"
17441
18313
  ).optional(),
17442
- draft: external_exports.boolean().describe(
17443
- "\u0417\u0430\u0431\u044B\u0442\u044C \u043D\u0430\u043F\u0438\u0441\u0430\u043D\u043D\u044B\u0439 \u0442\u043E\u0431\u043E\u0439 \u043F\u043B\u0430\u043D \u0438 \u0432\u0435\u0440\u043D\u0443\u0442\u044C\u0441\u044F \u043A \u043D\u0430\u0448\u0435\u043C\u0443 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0443"
18314
+ rebuild: external_exports.boolean().describe(
18315
+ "\u0421\u0440\u0430\u0437\u0443 \u0441\u043E\u0431\u0440\u0430\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u0430\u0432\u043A\u0438. \u041E\u0442\u0432\u0435\u0447\u0443 \u043D\u043E\u043C\u0435\u0440\u043E\u043C \u0437\u0430\u0434\u0430\u0447\u0438, \u0434\u0430\u043B\u044C\u0448\u0435 wait"
17444
18316
  ).optional(),
17445
18317
  step: external_exports.enum(["show", "mine", "insert", "remove", "replace"]).describe(
17446
- "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 \u041F\u041E\u0420\u042F\u0414\u041A\u041E\u041C \u0428\u0410\u0413\u041E\u0412 \u043C\u043E\u043D\u0442\u0430\u0436\u0430, \u0430 \u043D\u0435 \u0441 \u043F\u043B\u0430\u043D\u043E\u043C \u0440\u043E\u043B\u0438\u043A\u0430"
18318
+ "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 \u041F\u041E\u0420\u042F\u0414\u041A\u041E\u041C \u0428\u0410\u0413\u041E\u0412 \u043C\u043E\u043D\u0442\u0430\u0436\u0430, \u0430 \u043D\u0435 \u0441 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043C \u0440\u043E\u043B\u0438\u043A\u0430"
17447
18319
  ).optional(),
17448
18320
  name: external_exports.string().describe(
17449
18321
  '\u0418\u043C\u044F \u0428\u0410\u0413\u0410, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0441\u0442\u0430\u0432\u0438\u043C (\u0434\u043B\u044F step="insert"). \u041E\u0434\u043D\u043E \u0441\u043B\u043E\u0432\u043E \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 \u0432 mine/steps'
@@ -17462,54 +18334,31 @@ function planEditTool(engine, key) {
17462
18334
  const say = (message, where) => {
17463
18335
  ctx.issues.push({ code: "custom", message, input: asked, path: [where] });
17464
18336
  };
17465
- const planKeys = ["reel", "ops", "by", "rebuild", "confirm", "plan", "project", "draft"].filter((key2) => asked[key2] !== void 0);
17466
- if (asked.step !== void 0 && planKeys.length > 0) {
17467
- say(
17468
- `\xABstep\xBB \u043F\u0440\u0430\u0432\u0438\u0442 \u043F\u043E\u0440\u044F\u0434\u043E\u043A \u0448\u0430\u0433\u043E\u0432 \u043C\u043E\u043D\u0442\u0430\u0436\u0430, \u0430 \u043D\u0435 \u043F\u043B\u0430\u043D \u0440\u043E\u043B\u0438\u043A\u0430 \u2014 \u043B\u0438\u0448\u043D\u0435\u0435: ${planKeys.join(", ")}. \u041F\u043E\u0437\u043E\u0432\u0438 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E`,
17469
- "step"
17470
- );
17471
- return;
17472
- }
17473
- if (asked.step === void 0) {
17474
- const stepKeys = STEP_KEYS.filter((key2) => asked[key2] !== void 0);
17475
- if (stepKeys.length > 0) {
18337
+ const editKeys = ["reel", "ops", "stamps", "by", "rebuild"].filter((key2) => asked[key2] !== void 0);
18338
+ if (asked.step !== void 0) {
18339
+ if (editKeys.length > 0) {
17476
18340
  say(
17477
- `\xAB${stepKeys.join(", ")}\xBB \u043E\u0442\u043D\u043E\u0441\u044F\u0442\u0441\u044F \u043A \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044E \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0448\u0430\u0433\u043E\u043C \u2014 \u043D\u0430\u0437\u043E\u0432\u0438 \xABstep\xBB`,
18341
+ `\xABstep\xBB \u043F\u0440\u0430\u0432\u0438\u0442 \u043F\u043E\u0440\u044F\u0434\u043E\u043A \u0448\u0430\u0433\u043E\u0432 \u043C\u043E\u043D\u0442\u0430\u0436\u0430, \u0430 \u043D\u0435 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0440\u043E\u043B\u0438\u043A\u0430 \u2014 \u043B\u0438\u0448\u043D\u0435\u0435: ${editKeys.join(", ")}. \u041F\u043E\u0437\u043E\u0432\u0438 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E`,
17478
18342
  "step"
17479
18343
  );
17480
18344
  }
17481
- if (asked.reel === void 0) {
17482
- say(
17483
- "\u041D\u0435 \u0437\u0430\u0434\u0430\u043D \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u0439 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442 \xABreel\xBB: \u043F\u0440\u0430\u0432\u043A\u0430 \u043F\u043B\u0430\u043D\u0430 \u2014 \u044D\u0442\u043E \u0440\u043E\u043B\u0438\u043A \u0438 \u0442\u043E, \u0447\u0442\u043E \u0441 \u043D\u0438\u043C \u0434\u0435\u043B\u0430\u0435\u043C. \u041F\u043E\u0440\u044F\u0434\u043E\u043A \u0448\u0430\u0433\u043E\u0432 \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u043F\u0440\u0430\u0432\u0438\u0442\u0441\u044F \u043A\u043B\u044E\u0447\u043E\u043C \xABstep\xBB",
17484
- "reel"
17485
- );
17486
- }
17487
- if (asked.plan !== void 0 && asked.draft === true) {
17488
- say(
17489
- "\xABplan\xBB \u043A\u043B\u0430\u0434\u0451\u0442 \u0442\u0432\u043E\u0439 \u043F\u043B\u0430\u043D \u0440\u043E\u043B\u0438\u043A\u0430, \xABdraft\xBB \u0435\u0433\u043E \u0441\u0442\u0438\u0440\u0430\u0435\u0442 \u2014 \u044D\u0442\u043E \u0434\u0432\u0435 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u043F\u043E\u043B\u043E\u0436\u043D\u044B\u0435 \u043F\u0440\u043E\u0441\u044C\u0431\u044B. \u041E\u0441\u0442\u0430\u0432\u044C \u043E\u0434\u043D\u0443",
17490
- "draft"
17491
- );
17492
- }
17493
- if (asked.project !== void 0 && (asked.ops !== void 0 || asked.plan !== void 0 || asked.draft === true)) {
17494
- say(
17495
- "\xABproject\xBB \u2014 \u0432\u0435\u0441\u044C \u043E\u0431\u0449\u0438\u0439 naparnik-project/1; ops/plan/draft \u043F\u0440\u0430\u0432\u044F\u0442 \u0441\u0442\u0430\u0440\u044B\u0439 \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u043D\u044B\u0439 \u043F\u043B\u0430\u043D. \u041E\u0441\u0442\u0430\u0432\u044C \u043E\u0434\u043D\u0443 \u0432\u0435\u0442\u043A\u0443",
17496
- "project"
17497
- );
17498
- }
17499
- const asking = [
17500
- asked.ops !== void 0 ? "ops" : "",
17501
- asked.plan !== void 0 ? "plan" : "",
17502
- asked.project !== void 0 ? "project" : "",
17503
- asked.confirm === true ? "confirm" : "",
17504
- asked.draft === true ? "draft" : "",
17505
- asked.rebuild === true ? "rebuild" : ""
17506
- ].filter((one) => one !== "");
17507
- if (asking.length === 0) {
17508
- say(
17509
- "\u0414\u0435\u043B\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E: \u043D\u0438 \u043F\u0440\u0430\u0432\u043E\u043A, \u043D\u0438 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u044F. \xABops\xBB \u2014 \u043F\u0440\u0430\u0432\u043A\u0438 \u043F\u043B\u0430\u043D\u0430, \xABplan\xBB \u2014 \u0432\u0435\u0441\u044C \u043F\u043B\u0430\u043D \u0440\u043E\u043B\u0438\u043A\u0430 \u0446\u0435\u043B\u0438\u043A\u043E\u043C, \xABconfirm\xBB \u2014 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0441\u043A\u0430\u0437\u0430\u043B \xAB\u0434\u0430\xBB \u043D\u0430 \u043D\u044B\u043D\u0435\u0448\u043D\u044E\u044E \u0440\u0435\u0434\u0430\u043A\u0446\u0438\u044E, \xABdraft\xBB \u2014 \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u043D\u0430\u0448 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A. \u041F\u043E\u0440\u044F\u0434\u043E\u043A \u0448\u0430\u0433\u043E\u0432 \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u043F\u0440\u0430\u0432\u0438\u0442\u0441\u044F \u043A\u043B\u044E\u0447\u043E\u043C \xABstep\xBB",
17510
- "ops"
17511
- );
17512
- }
18345
+ return;
18346
+ }
18347
+ const stepKeys = STEP_KEYS.filter((key2) => asked[key2] !== void 0);
18348
+ if (stepKeys.length > 0) {
18349
+ say(`\xAB${stepKeys.join(", ")}\xBB \u043E\u0442\u043D\u043E\u0441\u044F\u0442\u0441\u044F \u043A \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044E \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0448\u0430\u0433\u043E\u043C \u2014 \u043D\u0430\u0437\u043E\u0432\u0438 \xABstep\xBB`, "step");
18350
+ }
18351
+ if (asked.reel === void 0) {
18352
+ say(
18353
+ "\u041D\u0435 \u0437\u0430\u0434\u0430\u043D \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u0439 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442 \xABreel\xBB: \u043F\u0440\u0430\u0432\u043A\u0430 \u2014 \u044D\u0442\u043E \u0440\u043E\u043B\u0438\u043A \u0438 \u0442\u043E, \u0447\u0442\u043E \u0441 \u043D\u0438\u043C \u0434\u0435\u043B\u0430\u0435\u043C. \u041F\u043E\u0440\u044F\u0434\u043E\u043A \u0448\u0430\u0433\u043E\u0432 \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u043F\u0440\u0430\u0432\u0438\u0442\u0441\u044F \u043A\u043B\u044E\u0447\u043E\u043C \xABstep\xBB",
18354
+ "reel"
18355
+ );
18356
+ }
18357
+ if (asked.ops === void 0 || asked.ops.length === 0) {
18358
+ say(
18359
+ '\u0414\u0435\u043B\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E: \u043D\u0435\u0442 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0439. \u041F\u0440\u0430\u0432\u043A\u0430 \u2014 ops \u0441\u043E \u0441\u043F\u0438\u0441\u043A\u043E\u043C \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0439, \xAB\u0434\u0430\xBB \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u2014 [{"op": "agree"}]. \u041F\u043E\u0440\u044F\u0434\u043E\u043A \u0448\u0430\u0433\u043E\u0432 \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u043F\u0440\u0430\u0432\u0438\u0442\u0441\u044F \u043A\u043B\u044E\u0447\u043E\u043C \xABstep\xBB',
18360
+ "ops"
18361
+ );
17513
18362
  }
17514
18363
  }),
17515
18364
  async execute(args, ctx) {
@@ -17522,352 +18371,21 @@ function planEditTool(engine, key) {
17522
18371
  with: args.with
17523
18372
  });
17524
18373
  }
17525
- return editPlan(
18374
+ return editDocument(
17526
18375
  engine,
17527
18376
  {
17528
18377
  reel: args.reel ?? "",
17529
18378
  ops: args.ops ?? [],
18379
+ stamps: args.stamps,
17530
18380
  by: args.by,
17531
- rebuild: args.rebuild,
17532
- confirm: args.confirm,
17533
- plan: args.plan,
17534
- project: args.project,
17535
- draft: args.draft
17536
- },
17537
- key,
17538
- ctx?.emitProgress
17539
- );
17540
- }
17541
- });
17542
- }
17543
- function universalOptions(asked) {
17544
- const found = /* @__PURE__ */ new Set();
17545
- const known = [
17546
- ["only_pauses", asked.onlyPauses],
17547
- ["style", asked.style],
17548
- ["preset", asked.preset],
17549
- ["model", asked.model],
17550
- ["tempo", asked.tempo],
17551
- ["keep_pauses", asked.keepPauses],
17552
- ["redo", asked.redo],
17553
- ["from_scratch", asked.fromScratch],
17554
- ["even_without_speech", asked.evenWithoutSpeech]
17555
- ];
17556
- for (const [name, value] of known) {
17557
- if (value !== void 0) found.add(name);
17558
- }
17559
- for (const [name, value] of Object.entries(asked.args ?? {})) {
17560
- if (name !== "reel" && value !== void 0) found.add(name);
17561
- }
17562
- return [...found].sort();
17563
- }
17564
- async function startRender(engine, asked, passport, key, tell) {
17565
- const place = engine.place();
17566
- if (place === null) return toolError(engine.whyNoEngine());
17567
- const where = fileOrRefusal(asked.video);
17568
- if ("refusal" in where) return toolError(where.refusal);
17569
- if (asked.reel === true) {
17570
- const viewed = await engine.command([
17571
- "composition-view",
17572
- "--project",
17573
- path(where.path),
17574
- "--json"
17575
- ]);
17576
- if (!viewed.ok) return toolError(viewed.text);
17577
- try {
17578
- const lines = viewed.text.trim().split("\n");
17579
- const answer = JSON.parse(lines[lines.length - 1] ?? "{}");
17580
- if (answer.universal === true) {
17581
- const extra = universalOptions(asked);
17582
- if (extra.length > 0) {
17583
- return toolError(
17584
- `\u041E\u0431\u0449\u0438\u0439 naparnik-project/1 \u0443\u0436\u0435 \u0445\u0440\u0430\u043D\u0438\u0442 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u0432 \u0441\u0430\u043C\u043E\u043C project, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 render \u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442 \u043A \u043D\u0435\u043C\u0443 legacy-\u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B: ${extra.join(", ")}.
17585
- \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u0432\u043D\u0435\u0441\u0438 \u044D\u0442\u0438 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432 project \u0447\u0435\u0440\u0435\u0437 plan_edit \u0438 \u043F\u043E\u0437\u043E\u0432\u0438 render \u0442\u043E\u043B\u044C\u043A\u043E \u0441 reel.`
17586
- );
17587
- }
17588
- return startComposition(engine, where.path, key, tell);
17589
- }
17590
- } catch {
17591
- return toolError(
17592
- "\u0414\u0432\u0438\u0436\u043E\u043A \u043D\u0435 \u0441\u043C\u043E\u0433 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C \u0432\u0438\u0434 \u043F\u0440\u043E\u0435\u043A\u0442\u0430. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043E\u0431\u043D\u043E\u0432\u0438 \u043D\u0430\u0447\u0438\u043D\u043A\u0443 \u0447\u0435\u0440\u0435\u0437 setup."
17593
- );
17594
- }
17595
- }
17596
- const onlyPauses = asked.onlyPauses === true;
17597
- const line = onlyPauses ? ["--without-subs"] : [];
17598
- if (asked.style !== void 0) line.push("--style", path(asked.style));
17599
- if (asked.preset !== void 0) line.push("--preset", asked.preset);
17600
- if (asked.model !== void 0) line.push("--model", asked.model);
17601
- if (asked.tempo !== void 0 && asked.tempo !== 1) line.push("--tempo", String(asked.tempo));
17602
- if (asked.keepPauses === true) line.push("--keep-pauses");
17603
- if (asked.fromScratch === true) line.push("--afresh");
17604
- if (asked.evenWithoutSpeech === true) line.push("--anyway");
17605
- if (asked.redo !== void 0 && asked.redo.length > 0) line.push("--only", ...asked.redo);
17606
- line.push(...readCapabilities(asked.args ?? {}, passport));
17607
- const what = onlyPauses ? `\u043F\u0430\u0443\u0437\u044B ${basename(where.path)}` : `\u043C\u043E\u043D\u0442\u0430\u0436 ${basename(where.path)}`;
17608
- return startMontage(engine, {
17609
- key,
17610
- place,
17611
- ...onlyPauses ? { ladder: PAUSES_LADDER } : {},
17612
- ...tell !== void 0 ? { tell } : {},
17613
- video: where.path,
17614
- what,
17615
- // ⚠ КЛЮЧИ ЭТОГО СЛОВАРЯ — ДВИЖКОВЫЕ, И ПЕРЕИМЕНОВЫВАТЬ ИХ НЕЛЬЗЯ. По ним
17616
- // движок узнаёт повторный вызов с той же настройкой — то самое «повторный
17617
- // монтаж почти ничего не считает». Имя инструмента тут прежнее (`montage`,
17618
- // `cut_pauses`) нарочно: смени его — и каждое задание выглядело бы новым, а
17619
- // кэш перестал бы срабатывать молча.
17620
- command: onlyPauses ? { "tool": "cut_pauses", "args": { style: asked.style, tempo: asked.tempo } } : {
17621
- "tool": "montage",
17622
- "args": {
17623
- style: asked.style,
17624
- preset: asked.preset,
17625
- model: asked.model,
17626
- tempo: asked.tempo,
17627
- keep_pauses: asked.keepPauses === true
17628
- }
17629
- },
17630
- ...asked.style !== void 0 ? { style: path(asked.style) } : {},
17631
- args: line,
17632
- hint: onlyPauses ? "\u0411\u044B\u0441\u0442\u0440\u043E, \u043E\u0431\u044B\u0447\u043D\u043E \u043C\u0435\u043D\u044C\u0448\u0435 \u043C\u0438\u043D\u0443\u0442\u044B." : "\u041F\u0435\u0440\u0432\u044B\u0439 \u0440\u0430\u0437 \u2014 \u043C\u0438\u043D\u0443\u0442\u044B; \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u044B\u0439 \u0441 \u0442\u043E\u0439 \u0436\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u043E\u0439 \u2014 \u0441\u0435\u043A\u0443\u043D\u0434\u044B."
17633
- });
17634
- }
17635
- async function startComposition(engine, reel, key, tell) {
17636
- const place = engine.place();
17637
- if (place === null) return toolError(engine.whyNoEngine());
17638
- const trouble = await engine.pythonTrouble();
17639
- if (trouble !== null) return toolError(trouble);
17640
- const python = engine.packPython();
17641
- if (python === null) {
17642
- return toolError("\u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u0435\u0449\u0451 \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0437\u043E\u0432\u0438 setup.");
17643
- }
17644
- if (tell !== void 0) {
17645
- tell("\u041F\u0440\u043E\u0432\u0435\u0440\u044E \u0442\u043E\u0447\u043D\u0443\u044E \u0440\u0435\u0434\u0430\u043A\u0446\u0438\u044E \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0438 \u0435\u0433\u043E \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A\u0438.");
17646
- tell("\u0421\u043E\u0431\u0435\u0440\u0443 \u044D\u0444\u0444\u0435\u043A\u0442\u044B, \u0432\u0438\u0434\u0435\u043E \u0438 \u0437\u0432\u0443\u043A \u043F\u043E \u043E\u0431\u0449\u0435\u0439 \u043A\u043E\u043C\u043F\u043E\u0437\u0438\u0446\u0438\u0438.");
17647
- }
17648
- const launch = await engine.background({
17649
- place,
17650
- executable: python,
17651
- key,
17652
- startJob: [
17653
- "job-create",
17654
- "--video",
17655
- reel,
17656
- "--composition",
17657
- "--what",
17658
- `\u043C\u043E\u043D\u0442\u0430\u0436 ${basename(reel)}`,
17659
- "--command",
17660
- JSON.stringify({ tool: "composition", args: {} })
17661
- ],
17662
- args: (id) => [
17663
- place.consolePy,
17664
- "composition-render",
17665
- "--project",
17666
- reel,
17667
- "--job",
17668
- id
17669
- ],
17670
- advice: "\u043F\u043E\u0437\u043E\u0432\u0438 setup \u2014 \u0441\u043A\u043E\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043E, \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043D\u0435\u043F\u043E\u043B\u043D\u043E\u0435"
17671
- });
17672
- if ("refusal" in launch) return toolError(launch.refusal);
17673
- return toolSuccess(
17674
- `\u0417\u0430\u0434\u0430\u0447\u0430 ${launch.id}: \u043C\u043E\u043D\u0442\u0430\u0436 ${basename(reel)}
17675
- \u0414\u0430\u043B\u044C\u0448\u0435 \u043F\u043E\u0437\u043E\u0432\u0438 wait \u0441 \u044D\u0442\u0438\u043C \u043D\u043E\u043C\u0435\u0440\u043E\u043C.`,
17676
- startedJob({
17677
- id: launch.id,
17678
- what: `\u043C\u043E\u043D\u0442\u0430\u0436 ${basename(reel)}`,
17679
- ahead: "\u041F\u0440\u043E\u0432\u0435\u0440\u044E \u043F\u0440\u043E\u0435\u043A\u0442. \u0421\u043E\u0431\u0435\u0440\u0443 \u044D\u0444\u0444\u0435\u043A\u0442\u044B, \u0432\u0438\u0434\u0435\u043E \u0438 \u0437\u0432\u0443\u043A.",
17680
- message: "\u041E\u0431\u0449\u0438\u0439 \u043F\u0440\u043E\u0435\u043A\u0442 \u0441\u043E\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044F \u043D\u0430 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F."
17681
- })
17682
- );
17683
- }
17684
- async function followWork(engine, asked, pace = realPace, tell) {
17685
- const ask2 = () => engine.command(["job", asked.job]);
17686
- const seconds2 = waitSeconds(asked.seconds ?? WAIT_LIMIT_SECONDS);
17687
- if (seconds2 === 0) return asJobResult(await ask2());
17688
- const state = await followJob({
17689
- ask: ask2,
17690
- seconds: seconds2,
17691
- pace,
17692
- // ⚠ Рассказ есть только если КЛИЕНТ его попросил (токен хода работы в
17693
- // `_meta`). Нет — ждём молча и отвечаем итогом: ждать всё равно дешевле
17694
- // пяти кругов переписки «спроси ещё раз».
17695
- ...tell !== void 0 ? { tell } : {}
17696
- });
17697
- if (state.ok && stillWorking(state.text)) {
17698
- const words = `${state.text}
17699
- \u0416\u0434\u0430\u043B ${seconds2} \u0441, \u0440\u0430\u0431\u043E\u0442\u0430 \u0435\u0449\u0451 \u0438\u0434\u0451\u0442. \u0421\u043A\u0430\u0436\u0438 \u044D\u0442\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u0438 \u043F\u043E\u0437\u043E\u0432\u0438 wait \u0435\u0449\u0451 \u0440\u0430\u0437 \u2014 \u0442\u0430\u043A \u043E\u043D \u0443\u0441\u043B\u044B\u0448\u0438\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0435 \u0448\u0430\u0433\u0438.`;
17700
- const job = readJob(state.text);
17701
- return job === null ? toolSuccess(words) : toolSuccess(words, job);
17702
- }
17703
- return asJobResult(state);
17704
- }
17705
- async function listJobs(engine, asked) {
17706
- return asToolResult(
17707
- await engine.command([
17708
- "jobs",
17709
- ...asked.video !== void 0 ? ["--video", path(asked.video)] : [],
17710
- ...asked.limit !== void 0 ? ["--count", String(asked.limit)] : []
17711
- ])
17712
- );
17713
- }
17714
- function renderDeclared(passport) {
17715
- return external_exports.object({
17716
- video: external_exports.string().describe(
17717
- "\u041F\u0443\u0442\u044C \u043A \u0432\u0438\u0434\u0435\u043E\u0444\u0430\u0439\u043B\u0443 \u043D\u0430 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430. \u0417\u0430\u0432\u0435\u0434\u0451\u043D\u043D\u044B\u0439 \u0440\u043E\u043B\u0438\u043A \u043C\u043E\u0436\u043D\u043E \u043D\u0430\u0437\u0432\u0430\u0442\u044C \u0438 \u043F\u0430\u043F\u043A\u043E\u0439 \u2014 \u043A\u043B\u044E\u0447\u043E\u043C reel"
17718
- ).optional(),
17719
- reel: external_exports.string().describe(
17720
- "\u041F\u0430\u043F\u043A\u0430 \u0437\u0430\u0432\u0435\u0434\u0451\u043D\u043D\u043E\u0433\u043E \u0440\u043E\u043B\u0438\u043A\u0430. \u0422\u043E \u0436\u0435, \u0447\u0442\u043E video, \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u0440\u043E\u043B\u0438\u043A\u0430 \u0446\u0435\u043B\u0438\u043A\u043E\u043C: \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A\u0438, \u0431\u0440\u0438\u0444 \u0438 \u043F\u0440\u0430\u0432\u043A\u0438 \u043F\u043B\u0430\u043D\u0430 \u0434\u0432\u0438\u0436\u043E\u043A \u0432\u043E\u0437\u044C\u043C\u0451\u0442 \u0438\u0437 \u043D\u0435\u0451 \u0441\u0430\u043C"
17721
- ).optional(),
17722
- only_pauses: external_exports.boolean().describe(
17723
- "\u0422\u043E\u043B\u044C\u043A\u043E \u0443\u0431\u0440\u0430\u0442\u044C \u043C\u043E\u043B\u0447\u0430\u043D\u0438\u0435 \u0438 \u0437\u0430\u043F\u0438\u043D\u043A\u0438. \u0420\u0435\u0447\u044C \u043F\u0440\u0438 \u044D\u0442\u043E\u043C \u041D\u0415 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0451\u0442\u0441\u044F \u0438 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0430 \u043D\u0435\u0442: \u0447\u0435\u043B\u043E\u0432\u0435\u043A, \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u0432\u0448\u0438\u0439 \xAB\u043F\u0440\u043E\u0441\u0442\u043E \u0443\u0431\u0435\u0440\u0438 \u0442\u0438\u0448\u0438\u043D\u0443\xBB, \u043D\u0435 \u0436\u0434\u0451\u0442 \u043C\u0438\u043D\u0443\u0442\u044B \u0438 \u043D\u0435 \u043A\u0430\u0447\u0430\u0435\u0442 \u043F\u043E\u043B\u0442\u043E\u0440\u0430 \u0433\u0438\u0433\u0430\u0431\u0430\u0439\u0442\u0430. \u0421\u0443\u0431\u0442\u0438\u0442\u0440\u044B \u0438 \u0433\u0440\u0430\u0444\u0438\u043A\u0443 \u044D\u0442\u0438\u043C \u043D\u0435 \u0441\u043E\u0431\u0440\u0430\u0442\u044C"
17724
- ).optional(),
17725
- style: external_exports.string().describe(
17726
- "\u041F\u0430\u043F\u043A\u0430 \u043F\u0430\u043A\u0430 \u0444\u0438\u0440\u043C\u0435\u043D\u043D\u043E\u0433\u043E \u0441\u0442\u0438\u043B\u044F. \u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D \u2014 \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0441\u0442\u0438\u043B\u044C \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E, \u0430 \u0435\u0441\u043B\u0438 \u0438 \u0435\u0433\u043E \u043D\u0435\u0442, \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0439 \u043D\u0435\u0439\u0442\u0440\u0430\u043B\u044C\u043D\u044B\u0439"
17727
- ).optional(),
17728
- preset: external_exports.string().describe(
17729
- "\u0418\u043C\u044F \u043F\u0440\u0435\u0441\u0435\u0442\u0430 \u0432\u0438\u0434\u0430 \u0418\u0417 \u041F\u0410\u041A\u0410 \u0421\u0422\u0418\u041B\u042F \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430. \u041D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u0438\u043C\u0435\u043D\u0430: \u0443 \u043D\u0435\u0433\u043E \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u043E\u0434\u0438\u043D \u043F\u0440\u0435\u0441\u0435\u0442 \u0438\u043B\u0438 \u0441\u0432\u043E\u0438"
17730
- ).optional(),
17731
- model: fromList(
17732
- passport?.["models"],
17733
- `\u041C\u043E\u0434\u0435\u043B\u044C \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u044F \u0440\u0435\u0447\u0438. ${aboutModels(passport)}`
17734
- ).optional(),
17735
- tempo: external_exports.number().describe("\u0423\u0441\u043A\u043E\u0440\u0435\u043D\u0438\u0435 \u0440\u0435\u0447\u0438, 1.0 \u2014 \u0431\u0435\u0437 \u0443\u0441\u043A\u043E\u0440\u0435\u043D\u0438\u044F").optional(),
17736
- keep_pauses: external_exports.boolean().describe("\u041D\u0435 \u0442\u0440\u043E\u0433\u0430\u0442\u044C \u043F\u0430\u0443\u0437\u044B, \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u044B").optional(),
17737
- redo: external_exports.array(fromList(passport?.["stages"], "\u0418\u043C\u044F \u0448\u0430\u0433\u0430 \u043C\u043E\u043D\u0442\u0430\u0436\u0430")).describe(
17738
- "\u0421\u043E\u0431\u0440\u0430\u0442\u044C \u043D\u0430\u0437\u0432\u0430\u043D\u043D\u044B\u0435 \u0448\u0430\u0433\u0438 \u0437\u0430\u043D\u043E\u0432\u043E, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u0433\u043E\u0442\u043E\u0432\u043E\u0435 \u0443\u0436\u0435 \u043B\u0435\u0436\u0438\u0442. \u041D\u0443\u0436\u043D\u043E \u0440\u0435\u0434\u043A\u043E \u2014 \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440, \u043A\u043E\u0433\u0434\u0430 \u043F\u043E\u0434\u043C\u0435\u043D\u0438\u043B\u0438 \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A \u0442\u0435\u043C \u0436\u0435 \u0438\u043C\u0435\u043D\u0435\u043C" + (passport !== null ? `. \u0428\u0430\u0433\u0438: ${passport["stages"].join(", ")}` : "")
17739
- ).optional(),
17740
- from_scratch: external_exports.boolean().describe(
17741
- "\u0421\u0447\u0438\u0442\u0430\u0442\u044C \u0432\u0441\u0451 \u0437\u0430\u043D\u043E\u0432\u043E, \u043A\u044D\u0448 \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0442\u044C. \u0414\u043E\u0440\u043E\u0433\u043E: \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0447\u0438 \u044D\u0442\u043E \u043C\u0438\u043D\u0443\u0442\u044B. \u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u0439 redo \u0441 \u043E\u0434\u043D\u0438\u043C \u0448\u0430\u0433\u043E\u043C"
17742
- ).optional(),
17743
- even_without_speech: external_exports.boolean().describe(
17744
- "\u0421\u043E\u0431\u0440\u0430\u0442\u044C, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u043C\u043E\u043D\u0442\u0430\u0436 \u0433\u043E\u0432\u043E\u0440\u0438\u0442 \xAB\u0440\u0435\u0447\u0438 \u0432 \u0444\u0430\u0439\u043B\u0435 \u043D\u0435\u0442\xBB. \u0421\u0442\u0430\u0432\u044C \u0422\u041E\u041B\u042C\u041A\u041E \u043F\u043E\u0441\u043B\u0435 \u0442\u043E\u0433\u043E, \u043A\u0430\u043A \u0441\u043F\u0440\u043E\u0441\u0438\u043B \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0438 \u043E\u043D \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u043B, \u0447\u0442\u043E \u0440\u0435\u0447\u044C \u0442\u0430\u043C \u0435\u0441\u0442\u044C: \u043D\u0430 \u043A\u043B\u0438\u043F\u0430\u0445 \u043F\u043E\u0434 \u043C\u0443\u0437\u044B\u043A\u0443 \u0434\u0432\u0438\u0436\u043E\u043A \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u044F \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0435\u0442 \u0441\u043B\u043E\u0432\u0430, \u0438 \u043E\u043D\u0438 \u043F\u043E\u043F\u0430\u0434\u0443\u0442 \u0432 \u043A\u0430\u0434\u0440"
17745
- ).optional()
17746
- });
17747
- }
17748
- var NOT_FOR_PAUSES = ["preset", "model", "keep_pauses", "redo", "even_without_speech"];
17749
- function renderArgs(passport) {
17750
- const ours = renderDeclared(passport);
17751
- const packKeys = capabilitiesToSchema(passport, new Set(Object.keys(ours.shape)));
17752
- return ours.extend(packKeys);
17753
- }
17754
- function renderTool(engine, passport, key) {
17755
- return defineTool({
17756
- name: "render",
17757
- title: "\u0421\u043E\u0431\u0440\u0430\u0442\u044C \u0440\u043E\u043B\u0438\u043A",
17758
- description: "\u0421\u043E\u0431\u0438\u0440\u0430\u0435\u0442 \u0440\u043E\u043B\u0438\u043A \u043D\u0430 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430: \u0443\u0431\u0438\u0440\u0430\u0435\u0442 \u043F\u0430\u0443\u0437\u044B, \u0441\u0442\u0430\u0432\u0438\u0442 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u044B \u043F\u043E \u0441\u043B\u043E\u0432\u0430\u043C, \u0440\u0438\u0441\u0443\u0435\u0442 \u0433\u0440\u0430\u0444\u0438\u043A\u0443 \u043F\u043E \u043F\u043B\u0430\u043D\u0443, \u0441\u0432\u043E\u0434\u0438\u0442 \u0437\u0432\u0443\u043A. \u0417\u043E\u0432\u0438 \u041F\u041E\u0421\u041B\u0415 \u0442\u043E\u0433\u043E, \u043A\u0430\u043A \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0441\u043A\u0430\u0437\u0430\u043B \xAB\u0434\u0430\xBB \u043D\u0430 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0438\u0437 plan: \u0441\u043E\u0431\u0440\u0430\u043D\u043D\u044B\u0439 \u043A\u0430\u0434\u0440 \u0441\u043F\u043E\u0440\u0438\u0442\u044C \u0434\u043E\u0440\u043E\u0436\u0435, \u0447\u0435\u043C \u0441\u0442\u0440\u043E\u0447\u043A\u0443 \u043F\u043B\u0430\u043D\u0430.\n\u0421\u043E\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044F \u0422\u041E\u041B\u042C\u041A\u041E \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u043F\u043B\u0430\u043D, \u0438 \xAB\u0434\u0430\xBB \u0432 \u0447\u0430\u0442\u0435 \u0441\u0430\u043C\u043E \u043F\u043E \u0441\u0435\u0431\u0435 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F: \u0437\u0430\u043F\u0438\u0448\u0438 \u0435\u0433\u043E \u0432\u044B\u0437\u043E\u0432\u043E\u043C plan_edit \u0441 confirm \u2014 \u0442\u0435\u043C \u0436\u0435, \u043A\u043E\u0442\u043E\u0440\u044B\u043C \u0448\u043B\u0451\u0448\u044C \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u044E\u044E \u043F\u0440\u0430\u0432\u043A\u0443. \u041B\u044E\u0431\u0430\u044F \u043F\u0440\u0430\u0432\u043A\u0430 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435 \u0441\u043D\u0438\u043C\u0430\u0435\u0442, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043F\u043E\u0440\u044F\u0434\u043E\u043A \u0442\u0430\u043A\u043E\u0439: \u043F\u043E\u043F\u0440\u0430\u0432\u0438\u043B, \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437\u0430\u043B \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443, \u0443\u0441\u043B\u044B\u0448\u0430\u043B \xAB\u0434\u0430\xBB, \u043F\u043E\u0437\u0432\u0430\u043B plan_edit \u0441 confirm, \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u0435\u043F\u0435\u0440\u044C render.\n\u0415\u0441\u043B\u0438 reel \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043E\u0431\u0449\u0438\u0439 naparnik-project/1, render \u0441\u0430\u043C \u0432\u044B\u0431\u0438\u0440\u0430\u0435\u0442 \u0443\u043D\u0438\u0432\u0435\u0440\u0441\u0430\u043B\u044C\u043D\u0443\u044E \u0441\u0431\u043E\u0440\u043A\u0443; \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0433\u043E \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430 \u0434\u043B\u044F \u043D\u0435\u0451 \u043D\u0435\u0442. \u0412\u0441\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0442\u0430\u043A\u043E\u0433\u043E \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u0443\u0436\u0435 \u043B\u0435\u0436\u0430\u0442 \u0432 project: \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 reel \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0439 style, preset, tempo \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B \u0441\u0442\u0430\u0440\u043E\u0433\u043E \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u2014 \u043D\u0443\u0436\u043D\u0443\u044E \u043F\u0440\u0430\u0432\u043A\u0443 \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0437\u0430\u043F\u0438\u0448\u0438 \u0447\u0435\u0440\u0435\u0437 plan_edit.\n\u041E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043D\u043E\u043C\u0435\u0440\u043E\u043C \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0434\u0430\u043B\u044C\u0448\u0435 wait, \u043E\u043D \u043F\u043E\u0434\u043E\u0436\u0434\u0451\u0442 \u0438 \u0440\u0430\u0441\u0441\u043A\u0430\u0436\u0435\u0442 \u0445\u043E\u0434. \u041F\u043E\u0432\u0442\u043E\u0440\u043D\u044B\u0439 \u0432\u044B\u0437\u043E\u0432 \u0441 \u0442\u043E\u0439 \u0436\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u043E\u0439 \u043F\u043E\u0447\u0442\u0438 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0434\u0435\u043B\u0430\u0435\u0442 \u0437\u0430\u043D\u043E\u0432\u043E: \u0432\u0441\u0451 \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0438\u0437 \u043A\u044D\u0448\u0430.\n\u041F\u0440\u043E\u0441\u044F\u0442 \xAB\u043F\u0440\u043E\u0441\u0442\u043E \u0443\u0431\u0435\u0440\u0438 \u043F\u0430\u0443\u0437\u044B\xBB, \xAB\u0443\u0431\u0435\u0440\u0438 \u0442\u0438\u0448\u0438\u043D\u0443\xBB \u2014 only_pauses=true: \u0440\u0435\u0447\u044C \u043F\u0440\u0438 \u044D\u0442\u043E\u043C \u043D\u0435 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0451\u0442\u0441\u044F, \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0430 \u043D\u0435\u0442 \u0438 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E. \u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u0432 \u043D\u0430\u0447\u0430\u043B\u0435 (plan_edit, \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044F intro) \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0438 \u0437\u0434\u0435\u0441\u044C: \u043E\u043D\u0430 \u043D\u0435 \u043F\u0440\u043E \u0440\u0435\u0447\u044C, \u0430 \u043F\u0440\u043E \u0442\u043E, \u0447\u0442\u043E \u0432\u0441\u0442\u0430\u0451\u0442 \u041F\u0415\u0420\u0415\u0414 \u0440\u043E\u043B\u0438\u043A\u043E\u043C.\n\u0420\u0430\u0431\u043E\u0442\u0430 \u0438\u0434\u0451\u0442 \u043D\u0430 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430, \u043A \u043D\u0430\u043C \u0432\u0438\u0434\u0435\u043E \u043D\u0435 \u0443\u0445\u043E\u0434\u0438\u0442 \u2014 \u043D\u0430\u0432\u0435\u0440\u0445 \u0438\u0434\u0451\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438 \u043F\u043E \u043A\u043B\u044E\u0447\u0443.",
17759
- // Полоса хода работы: монтаж идёт минутами, и всё это время в ленте не
17760
- // происходило бы ничего. ⚠ Ожидание — у `wait`, и полосы там нет: иначе
17761
- // каждая проверка рисовала бы новую поверх этой.
17762
- ui: { resourceUri: RUNBAR_RESOURCE_URI },
17763
- // ⚠ КЛЮЧИ ПАКА ДОБАВЛЯЮТСЯ К СХЕМЕ ЧЕРЕЗ `extend`, А ТИП ОСТАЁТСЯ ОТ
17764
- // ОБЪЯВЛЕННОЙ ЧАСТИ. Их набор известен только в рантайме (паспорт пака), и
17765
- // выведенный из них тип превратился бы в словарь со свободными ключами —
17766
- // тогда `args.video` перестал бы быть строкой, а описка в имени поля
17767
- // перестала бы быть ошибкой компиляции. В объявление модели они при этом
17768
- // попадают полностью: схема-то настоящая.
17769
- args: renderArgs(passport).check((ctx) => {
17770
- const asked = ctx.value;
17771
- const say = (message, where) => {
17772
- ctx.issues.push({ code: "custom", message, input: asked, path: [where] });
17773
- };
17774
- const video = asked["video"];
17775
- const reel = asked["reel"];
17776
- if (video === void 0 && reel === void 0) {
17777
- say(
17778
- "\u041D\u0435 \u0441\u043A\u0430\u0437\u0430\u043D\u043E, \u0447\u0442\u043E \u0441\u043E\u0431\u0438\u0440\u0430\u0442\u044C: \u043D\u0430\u0437\u043E\u0432\u0438 video (\u0444\u0430\u0439\u043B) \u0438\u043B\u0438 reel (\u043F\u0430\u043F\u043A\u0443 \u0437\u0430\u0432\u0435\u0434\u0451\u043D\u043D\u043E\u0433\u043E \u0440\u043E\u043B\u0438\u043A\u0430). \u0427\u0442\u043E \u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0435\u0441\u0442\u044C, \u043F\u043E\u043A\u0430\u0436\u0435\u0442 plan \u0431\u0435\u0437 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u043E\u0432",
17779
- "video"
17780
- );
17781
- }
17782
- if (video !== void 0 && reel !== void 0) {
17783
- say("\xABvideo\xBB \u0438 \xABreel\xBB \u2014 \u044D\u0442\u043E \u043E\u0434\u043D\u043E \u0438 \u0442\u043E \u0436\u0435 \u043C\u0435\u0441\u0442\u043E, \u043D\u0430\u0437\u0432\u0430\u043D\u043D\u043E\u0435 \u0434\u0432\u0430\u0436\u0434\u044B. \u041E\u0441\u0442\u0430\u0432\u044C \u043E\u0434\u043D\u043E", "reel");
17784
- }
17785
- if (asked["only_pauses"] !== true) return;
17786
- const extra = NOT_FOR_PAUSES.filter((name) => asked[name] !== void 0);
17787
- if (extra.length > 0) {
17788
- say(
17789
- `\xABonly_pauses\xBB \u0440\u0435\u0436\u0435\u0442 \u0442\u0438\u0448\u0438\u043D\u0443 \u0438 \u043D\u0430 \u044D\u0442\u043E\u043C \u043E\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u2014 \u043B\u0438\u0448\u043D\u0435\u0435: ${extra.join(", ")}. \u041F\u043E\u0437\u043E\u0432\u0438 \u043B\u0438\u0431\u043E \u0441 only_pauses, \u043B\u0438\u0431\u043E \u0437\u0430 \u043F\u043E\u043B\u043D\u044B\u043C \u043C\u043E\u043D\u0442\u0430\u0436\u043E\u043C`,
17790
- "only_pauses"
17791
- );
17792
- }
17793
- }),
17794
- async execute(args, ctx) {
17795
- const all = args;
17796
- const video = args.video ?? args.reel ?? "";
17797
- return startRender(
17798
- engine,
17799
- {
17800
- video,
17801
- reel: args.reel !== void 0,
17802
- onlyPauses: args.only_pauses,
17803
- style: args.style,
17804
- preset: args.preset,
17805
- model: args.model,
17806
- tempo: args.tempo,
17807
- keepPauses: args.keep_pauses,
17808
- redo: args.redo,
17809
- fromScratch: args.from_scratch,
17810
- evenWithoutSpeech: args.even_without_speech,
17811
- args: all
18381
+ rebuild: args.rebuild
17812
18382
  },
17813
- passport,
17814
18383
  key,
17815
18384
  ctx?.emitProgress
17816
18385
  );
17817
18386
  }
17818
18387
  });
17819
18388
  }
17820
- function waitTool(engine, pace = realPace) {
17821
- return defineTool({
17822
- name: "wait",
17823
- title: "\u0414\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F \u0440\u0430\u0431\u043E\u0442\u044B",
17824
- description: `\u0416\u0434\u0451\u0442 \u0437\u0430\u0434\u0430\u0447\u0443 \u0438 \u043F\u043E \u0445\u043E\u0434\u0443 \u0440\u0430\u0441\u0441\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u043A\u0430\u043A\u043E\u0439 \u0448\u0430\u0433 \u043F\u043E\u0448\u0451\u043B \u0438 \u043A\u0430\u043A\u043E\u0439 \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B\u0441\u044F \u2014 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437\u044B\u0432\u0430\u0439 \u044D\u0442\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u0441\u0440\u0430\u0437\u0443, \u043D\u0435 \u0434\u043E\u0436\u0438\u0434\u0430\u044F\u0441\u044C \u043A\u043E\u043D\u0446\u0430. \u0422\u0430\u043A \u0438 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0439: \u043E\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C \u043F\u043E \u043A\u0440\u0443\u0433\u0443 \u043D\u0435 \u043D\u0430\u0434\u043E, \u0436\u0434\u0451\u0442 \u043E\u043D \u0434\u043E ${WAIT_LIMIT_SECONDS} \u0441\u0435\u043A\u0443\u043D\u0434 \u0438 \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0440\u0430\u043D\u044C\u0448\u0435, \u0435\u0441\u043B\u0438 \u0440\u0430\u0431\u043E\u0442\u0430 \u043A\u043E\u043D\u0447\u0438\u0442\u0441\u044F.
17825
- \u0411\u0435\u0437 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u043D\u043E\u043C\u0435\u0440, \u0440\u043E\u043B\u0438\u043A, \u0447\u0435\u043C \u043A\u043E\u043D\u0447\u0438\u043B\u043E\u0441\u044C. \u0417\u043E\u0432\u0438, \u043A\u043E\u0433\u0434\u0430 \u043D\u043E\u043C\u0435\u0440 \u043F\u043E\u0442\u0435\u0440\u044F\u043B\u0441\u044F: \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440, \u043F\u043E\u0441\u043B\u0435 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B \u0438\u043B\u0438 \u043A\u043E\u0433\u0434\u0430 \u0432\u044B\u0437\u043E\u0432 \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0441\u044F \u043F\u043E\u0441\u0440\u0435\u0434\u0438 \u0440\u0430\u0431\u043E\u0442\u044B. \u0421 video \u2014 \u0437\u0430\u0434\u0430\u0447\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0440\u043E\u043B\u0438\u043A\u0430.
17826
- \u0417\u0430\u0434\u0430\u0447\u0438 \u043B\u0435\u0436\u0430\u0442 \u043D\u0430 \u0434\u0438\u0441\u043A\u0435 \u0438 \u043F\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u044E\u0442 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B. \u041F\u043E\u0434\u043F\u0438\u0441\u043A\u0430 \u043D\u0435 \u043D\u0443\u0436\u043D\u0430: \u0437\u0430\u0431\u0440\u0430\u0442\u044C \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435 \u043C\u043E\u0436\u043D\u043E \u0432\u0441\u0435\u0433\u0434\u0430.`,
17827
- args: external_exports.object({
17828
- job: external_exports.string().describe(
17829
- "\u041D\u043E\u043C\u0435\u0440 \u0437\u0430\u0434\u0430\u0447\u0438, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 2026-08-19T14-03-12Z-a3f1. \u0415\u0433\u043E \u043D\u0430\u0437\u0432\u0430\u043B \u0442\u043E\u0442 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442, \u0447\u0442\u043E \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B \u0440\u0430\u0431\u043E\u0442\u0443"
17830
- ).optional(),
17831
- seconds: external_exports.number().describe(
17832
- `\u0421\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0435\u043A\u0443\u043D\u0434 \u0436\u0434\u0430\u0442\u044C. \u041F\u043E\u0442\u043E\u043B\u043E\u043A ${WAIT_LIMIT_SECONDS}: \u043F\u0440\u043E\u0441\u044C\u0431\u0430 \u0431\u043E\u043B\u044C\u0448\u0435 \u043C\u043E\u043B\u0447\u0430 \u043E\u0431\u0440\u0435\u0436\u0435\u0442\u0441\u044F. \u0411\u0435\u0437 \u043D\u0435\u0433\u043E \u0436\u0434\u0451\u0442 \u0441\u0442\u043E\u043B\u044C\u043A\u043E, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043C\u043E\u0436\u043D\u043E`
17833
- ).optional(),
17834
- video: external_exports.string().describe("\u0422\u043E\u043B\u044C\u043A\u043E \u0437\u0430\u0434\u0430\u0447\u0438 \u044D\u0442\u043E\u0433\u043E \u0440\u043E\u043B\u0438\u043A\u0430 (\u0431\u0435\u0437 job)").optional(),
17835
- limit: external_exports.number().describe("\u0421\u043A\u043E\u043B\u044C\u043A\u043E \u0437\u0430\u0434\u0430\u0447 \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C, \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E 15").optional()
17836
- }).check((ctx) => {
17837
- const asked = ctx.value;
17838
- if (asked.job === void 0) {
17839
- if (asked.seconds !== void 0) {
17840
- ctx.issues.push({
17841
- code: "custom",
17842
- message: "\xABseconds\xBB \u2014 \u044D\u0442\u043E \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0436\u0434\u0430\u0442\u044C \u041E\u0414\u041D\u0423 \u0437\u0430\u0434\u0430\u0447\u0443: \u043D\u0430\u0437\u043E\u0432\u0438 \u0435\u0451 \u043D\u043E\u043C\u0435\u0440 \u0432 \xABjob\xBB",
17843
- input: asked,
17844
- path: ["seconds"]
17845
- });
17846
- }
17847
- return;
17848
- }
17849
- for (const key of ["video", "limit"]) {
17850
- if (asked[key] !== void 0) {
17851
- ctx.issues.push({
17852
- code: "custom",
17853
- message: `\xAB${key}\xBB \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0441\u044F \u043A \u0421\u041F\u0418\u0421\u041A\u0423 \u0437\u0430\u0434\u0430\u0447, \u0430 \u0441 \xABjob\xBB \u0436\u0434\u0443\u0442 \u043E\u0434\u043D\u0443. \u041F\u043E\u0437\u043E\u0432\u0438 \u0431\u0435\u0437 \xABjob\xBB, \u0447\u0442\u043E\u0431\u044B \u0443\u0432\u0438\u0434\u0435\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A`,
17854
- input: asked,
17855
- path: [key]
17856
- });
17857
- }
17858
- }
17859
- }),
17860
- readOnly: true,
17861
- async execute(args, ctx) {
17862
- const place = engine.place();
17863
- if (place === null) return toolError(engine.whyNoEngine());
17864
- if (args.job === void 0) {
17865
- return listJobs(engine, { video: args.video, limit: args.limit });
17866
- }
17867
- return followWork(engine, { job: args.job, seconds: args.seconds }, pace, ctx?.emitProgress);
17868
- }
17869
- });
17870
- }
17871
18389
  async function showFrame(engine, asked) {
17872
18390
  const video = asked.video;
17873
18391
  const moment = asked.moment;
@@ -18002,10 +18520,10 @@ var OWN_STYLE_FOLDER = "style";
18002
18520
  function contractRefusal(theirs) {
18003
18521
  return `\u0414\u0432\u0438\u0436\u043E\u043A \u0433\u043E\u0432\u043E\u0440\u0438\u0442 \u043D\u0430 \u0434\u043E\u0433\u043E\u0432\u043E\u0440\u0435 \xAB${theirs}\xBB, \u0430 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u0435 \u043F\u043E\u043D\u0438\u043C\u0430\u0435\u0442 \xAB${CHOICES_CONTRACT}\xBB. \u041E\u0431\u043D\u043E\u0432\u0438 \u0442\u043E, \u0447\u0442\u043E \u0441\u0442\u0430\u0440\u0435\u0435: setup \u0441 what=engine \u0434\u043B\u044F \u0434\u0432\u0438\u0436\u043A\u0430, what=shell \u0434\u043B\u044F \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F.`;
18004
18522
  }
18005
- function notJson(text) {
18006
- const head = text.trim().slice(0, 160).replace(/\s+/g, " ");
18007
- return NOT_JSON2 + (head ? `
18008
- \u0427\u0442\u043E \u043F\u0440\u0438\u0448\u043B\u043E \u0432\u043C\u0435\u0441\u0442\u043E JSON: \xAB${head}\u2026\xBB` : "");
18523
+ function notJson(text2) {
18524
+ const head2 = text2.trim().slice(0, 160).replace(/\s+/g, " ");
18525
+ return NOT_JSON2 + (head2 ? `
18526
+ \u0427\u0442\u043E \u043F\u0440\u0438\u0448\u043B\u043E \u0432\u043C\u0435\u0441\u0442\u043E JSON: \xAB${head2}\u2026\xBB` : "");
18009
18527
  }
18010
18528
  var NOT_JSON2 = "\u0414\u0432\u0438\u0436\u043E\u043A \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043D\u0435 \u0442\u0435\u043C, \u0447\u0435\u0433\u043E \u0436\u0434\u0430\u043B\u0438. \u041E\u0431\u043D\u043E\u0432\u0438 \u0434\u0432\u0438\u0436\u043E\u043A \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C setup \u0441 what=engine \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438.";
18011
18529
  var FIRST_BATCH = 12;
@@ -18353,7 +18871,7 @@ async function showCards(engine, input) {
18353
18871
  // «Готово» требовало ответить и там. Нашло ревью устройства
18354
18872
  // 2026-09-18.
18355
18873
  only: wanted2,
18356
- clip: basename3(where.path),
18874
+ clip: basename5(where.path),
18357
18875
  video: where.path,
18358
18876
  // ⚠ СЫРЬЁ ПРЕВЬЮ ЕДЕТ ФОРМЕ ЦЕЛИКОМ: чистый кадр, числа вида и
18359
18877
  // умолчания движка. Картинку она складывает сама (решение владельца
@@ -18432,7 +18950,7 @@ function questionInWords(q) {
18432
18950
  return `${q.title} ${shown}${rest}`;
18433
18951
  }
18434
18952
  var LATIN_NAME = /^[a-z][a-z0-9_-]{0,31}$/;
18435
- function basename3(path2) {
18953
+ function basename5(path2) {
18436
18954
  const parts = path2.split(/[\\/]/);
18437
18955
  return parts[parts.length - 1] ?? path2;
18438
18956
  }
@@ -21119,44 +21637,6 @@ function chooseWidgetHtml() {
21119
21637
  </body>
21120
21638
  </html>`;
21121
21639
  }
21122
- var CALLS_FILE = "calls.jsonl";
21123
- var CALLS_PREVIOUS = "calls.1.jsonl";
21124
- var CALLS_CAP_BYTES = 1e6;
21125
- function withCallLog(home, now = () => Date.now()) {
21126
- return (tool) => {
21127
- const original = tool.execute.bind(tool);
21128
- return {
21129
- ...tool,
21130
- async execute(input, ...rest) {
21131
- const started = now();
21132
- const result = await original(input, ...rest);
21133
- write(home, {
21134
- ts: (/* @__PURE__ */ new Date()).toISOString(),
21135
- tool: tool.name,
21136
- ok: result.type === "success",
21137
- ms: now() - started
21138
- });
21139
- return result;
21140
- }
21141
- };
21142
- };
21143
- }
21144
- function write(home, line) {
21145
- try {
21146
- mkdirSync(home, { recursive: true });
21147
- const file2 = join(home, CALLS_FILE);
21148
- rotate(file2, join(home, CALLS_PREVIOUS));
21149
- appendFileSync(file2, `${JSON.stringify(line)}
21150
- `, { encoding: "utf8", mode: 384 });
21151
- } catch {
21152
- }
21153
- }
21154
- function rotate(file2, previous) {
21155
- try {
21156
- if (statSync(file2).size >= CALLS_CAP_BYTES) renameSync(file2, previous);
21157
- } catch {
21158
- }
21159
- }
21160
21640
 
21161
21641
  // src/update-nudge.ts
21162
21642
  function usable2(value) {
@@ -21317,7 +21797,7 @@ ${result.content}` : [{ type: "text", text: header }, ...result.content]
21317
21797
  }
21318
21798
 
21319
21799
  // src/tool-registry.ts
21320
- function buildAll(client, engine, passport, key, baseUrl, pace = realPace) {
21800
+ function buildAll(client, engine, passport, key, baseUrl, pace = realPace, awake = ASLEEP, crashes = null) {
21321
21801
  return [
21322
21802
  // Порядок — это порядок работы, и модель читает список сверху вниз:
21323
21803
  // проверить машину, придумать словами, поправить словами, собрать, дождаться.
@@ -21349,7 +21829,16 @@ function buildAll(client, engine, passport, key, baseUrl, pace = realPace) {
21349
21829
  // ходы МОДЕЛИ, а ход, отбитый опиской или мёртвой подпиской, стоил тех же
21350
21830
  // секунд. Встань счётчик внутрь — из замера выпали бы именно неудачные
21351
21831
  // ходы, то есть самые дорогие (`call-log.ts`).
21352
- ].map(withSubscription(client)).map(withArgs).map(withCallLog(engine.home()));
21832
+ //
21833
+ // ⚠ «НЕ СПИ» — СНАРУЖИ СВЕРКИ И ЗАСЛОНА, ПОД СЧЁТЧИКОМ. Держит любой ход
21834
+ // модели, в том числе отбитый: раз модель зовёт — она работает, и хвост
21835
+ // бодрости после него нужен так же (`keep-awake.ts`). Счётчик остаётся
21836
+ // самым внешним по доводу выше.
21837
+ //
21838
+ // ⚠ ЛОВУШКА ПОЛОМОК — СНАРУЖИ СВЕРКИ И ЗАСЛОНА: падение в самом заслоне
21839
+ // подписки — тоже наш баг, и его надо поймать. Под счётчиком — чтобы
21840
+ // пойманный вызов лёг в журнал отказом, а не пропал из замера.
21841
+ ].map(withSubscription(client)).map(withArgs).map(withCrashReport(crashes)).map(withWakefulness(awake)).map(withCallLog(engine.home()));
21353
21842
  }
21354
21843
  function buildResources() {
21355
21844
  return [chooseResource(), runbarResource()];
@@ -21402,8 +21891,8 @@ function buildBlock(entryFile, key) {
21402
21891
  function withoutBom(string5) {
21403
21892
  return string5.charCodeAt(0) === 65279 ? string5.slice(1) : string5;
21404
21893
  }
21405
- function findBlock(text) {
21406
- const lines = text.split("\n");
21894
+ function findBlock(text2) {
21895
+ const lines = text2.split("\n");
21407
21896
  const heading = new RegExp(`^\\[mcp_servers\\.(?:${SERVER_NAME}|"${SERVER_NAME}")\\]\\s*$`);
21408
21897
  const child = new RegExp(`^\\[mcp_servers\\.(?:${SERVER_NAME}|"${SERVER_NAME}")\\.`);
21409
21898
  const i = lines.findIndex((s) => heading.test(s));
@@ -21428,8 +21917,8 @@ function keyFromBlock(blockText) {
21428
21917
  }
21429
21918
  return void 0;
21430
21919
  }
21431
- function keyInTable(text) {
21432
- const lines = text.split("\n");
21920
+ function keyInTable(text2) {
21921
+ const lines = text2.split("\n");
21433
21922
  let inTable = false;
21434
21923
  for (const raw of lines) {
21435
21924
  const s = withoutBom(raw);
@@ -21546,4 +22035,4 @@ ${inTable}`, file2);
21546
22035
  };
21547
22036
  }
21548
22037
 
21549
- export { 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, 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, refusalFor, remove, rollback, rollbackShell, shellDir, tarEntries, tidyOld2 as tidyOld, timeToCheck, timeToCheck2, unpackArchive, updateEngine, withArgs };
22038
+ 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, rollback, rollbackShell, shellDir, systemBlocker, tarEntries, tidyOld2 as tidyOld, timeToCheck, timeToCheck2, unpackArchive, updateEngine, watchProcessCrashes, withArgs };