@ru-code/ru-code 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.mjs CHANGED
@@ -15294,20 +15294,7 @@ const layer$20 = sync$2(NetService, make$34);
15294
15294
 
15295
15295
  //#endregion
15296
15296
  //#region package.json
15297
- var version = "2.0.0";
15298
-
15299
- //#endregion
15300
- //#region ../../packages/contracts/src/appName.ts
15301
- /**
15302
- * APP_NAME — single source of truth for the application's short name.
15303
- *
15304
- * Used to derive every user-visible identifier that should be branded with
15305
- * the app: the home directory (`~/.${APP_NAME}`), env var prefixes, temp
15306
- * file prefixes, etc. Change here to rebrand everywhere.
15307
- */
15308
- const APP_NAME$1 = "ru-fork";
15309
- /** `~/.${APP_NAME}` directory name (without leading `/`). */
15310
- const APP_HOME_DIRNAME = `.${APP_NAME$1}`;
15297
+ var version = "3.0.0";
15311
15298
 
15312
15299
  //#endregion
15313
15300
  //#region ../../packages/contracts/src/baseSchemas.ts
@@ -15772,6 +15759,17 @@ var OpenError = class extends TaggedErrorClass()("OpenError", {
15772
15759
  */
15773
15760
  const APP_NAME = "Ru Code";
15774
15761
  /**
15762
+ * APP_HOME_SLUG — kebab-case slug used to derive on-disk identifiers: the
15763
+ * per-user home dot-directory (`~/.ru-fork`), env-var prefixes and temp-file
15764
+ * prefixes. Distinct from {@link APP_NAME} (the display name) because these
15765
+ * paths must be filesystem- and shell-safe and stable across re-skins. This is
15766
+ * the one and only place the home-dir slug lives; the installer and the app's
15767
+ * base-dir resolution both derive from it so they cannot diverge.
15768
+ */
15769
+ const APP_HOME_SLUG = "ru-fork";
15770
+ /** `~/.${APP_HOME_SLUG}` directory name (without leading `/`), e.g. `.ru-fork`. */
15771
+ const APP_HOME_DIRNAME = `.${APP_HOME_SLUG}`;
15772
+ /**
15775
15773
  * CLI_NAME — name of the underlying CLI binary / ACP provider. This is the one
15776
15774
  * and only place the CLI name literal lives. Used for the spawned binary, the
15777
15775
  * provider kind id, user-facing labels and config-directory derivation.
@@ -20040,10 +20038,13 @@ var ServerConfig = class ServerConfig extends Service()("t3/config/ServerConfig"
20040
20038
  const baseDir = typeof baseDirOrPrefix === "string" ? baseDirOrPrefix : yield* fs.makeTempDirectoryScoped({ prefix: baseDirOrPrefix.prefix });
20041
20039
  const derivedPaths = yield* deriveServerPaths(baseDir, devUrl);
20042
20040
  yield* ensureServerDirectories(derivedPaths);
20041
+ const path = yield* Path;
20043
20042
  return {
20044
20043
  logLevel: "Error",
20045
20044
  cwd,
20046
20045
  baseDir,
20046
+ cliJs: path.join(baseDir, "cli.js"),
20047
+ cliConfigDir: path.join(path.dirname(baseDir), CLI_FOLDER),
20047
20048
  ...derivedPaths,
20048
20049
  mode: "web",
20049
20050
  autoBootstrapProjectFromCwd: false,
@@ -25118,6 +25119,449 @@ const resolveBaseDir = fn(function* (raw) {
25118
25119
  return resolve(yield* expandHomePath$4(raw.trim()));
25119
25120
  });
25120
25121
 
25122
+ //#endregion
25123
+ //#region src/ru-fork/preflight/common/constants.ts
25124
+ /** CLI config dir name, e.g. ".qwen". */
25125
+ const CLI_DIR = CLI_CONFIG_DIRNAME;
25126
+ /** Our app home dir name, e.g. ".ru-fork". */
25127
+ const APP_DIR = APP_HOME_DIRNAME;
25128
+ const NODE_ENGINE_RANGE = "^22.16 || ^23.11 || >=24.10";
25129
+ /** Minimum CLI version; "" disables the version check (presence only). */
25130
+ const CLI_MIN_VERSION = "0.13.1";
25131
+ const CLI_PROBE_TIMEOUT_MS = 15e3;
25132
+ const GIT_PROBE_TIMEOUT_MS = 5e3;
25133
+
25134
+ //#endregion
25135
+ //#region src/ru-fork/preflight/common/messages.ts
25136
+ const MESSAGES = {
25137
+ CONFIG_NOT_FOUND: "Каталог конфигурации CLI не найден. Проверены пути:",
25138
+ SOURCES_DISAGREE: "Два источника расположения cli.js не совпадают (bin и .install-dir указывают на разное).",
25139
+ INSTALL_DIR_NOWHERE: "Файл .install-dir указывает на несуществующий cli.js.",
25140
+ CLI_NOT_FOUND: "cli.js не найден. Проверены пути:",
25141
+ NODE_OK: "Node.js {found} ✓",
25142
+ NODE_LOW: `Node.js {found} установлен, требуется ${NODE_ENGINE_RANGE}. Обновите: https://nodejs.org/`,
25143
+ GIT_OK: "Git {found} ✓",
25144
+ GIT_MISSING: "Git не найден на PATH. Установите Git: https://git-scm.com/downloads",
25145
+ GIT_BROKEN: "Git установлен, но `git --version` завершилась с ошибкой или превысила тайм-аут.",
25146
+ CLI_OK: "CLI {found} ✓",
25147
+ CLI_BROKEN: "CLI установлен, но `cli.js --version` завершилась с ошибкой или превысила тайм-аут.",
25148
+ CLI_LOW: `CLI {found} установлен, требуется ≥ ${CLI_MIN_VERSION}. Обновите: npm install -g ${CLI_NPM_PACKAGE}@latest`,
25149
+ FOOTER_FAIL: "Установите недостающие компоненты и перезапустите."
25150
+ };
25151
+
25152
+ //#endregion
25153
+ //#region src/ru-fork/preflight/common/render.ts
25154
+ const render = (template, values) => template.replace(/\{(\w+)\}/g, (_, k) => values[k] ?? `{${k}}`);
25155
+
25156
+ //#endregion
25157
+ //#region src/ru-fork/preflight/common/version.ts
25158
+ const parseVersion = (input) => {
25159
+ const match = /(\d+)\.(\d+)(?:\.(\d+))?/.exec(input);
25160
+ if (!match) return null;
25161
+ return [
25162
+ Number(match[1]),
25163
+ Number(match[2]),
25164
+ Number(match[3] ?? 0)
25165
+ ];
25166
+ };
25167
+ /** Three-component numeric `actual >= minimum`. */
25168
+ const isAtLeast = (actual, minimum) => {
25169
+ const actualParts = parseVersion(actual);
25170
+ const minimumParts = parseVersion(minimum);
25171
+ if (!actualParts || !minimumParts) return false;
25172
+ const [actualMajor, actualMinor, actualPatch] = actualParts;
25173
+ const [minimumMajor, minimumMinor, minimumPatch] = minimumParts;
25174
+ if (actualMajor !== minimumMajor) return actualMajor > minimumMajor;
25175
+ if (actualMinor !== minimumMinor) return actualMinor > minimumMinor;
25176
+ return actualPatch >= minimumPatch;
25177
+ };
25178
+ /** Does `actual` satisfy an `^X.Y[.Z] || >=X.Y[.Z]` range? */
25179
+ const satisfiesRange = (actual, range) => {
25180
+ const actualParts = parseVersion(actual);
25181
+ if (!actualParts) return false;
25182
+ const [actualMajor, actualMinor, actualPatch] = actualParts;
25183
+ return range.split("||").some((rawDisjunct) => {
25184
+ const disjunct = rawDisjunct.trim();
25185
+ const caretMatch = /^\^(\d+)\.(\d+)(?:\.(\d+))?$/.exec(disjunct);
25186
+ if (caretMatch) return actualMajor === Number(caretMatch[1]) && actualMinor >= Number(caretMatch[2]);
25187
+ const atLeastMatch = /^>=(\d+)\.(\d+)(?:\.(\d+))?$/.exec(disjunct);
25188
+ if (atLeastMatch) {
25189
+ const targetMajor = Number(atLeastMatch[1]);
25190
+ const targetMinor = Number(atLeastMatch[2]);
25191
+ const targetPatch = Number(atLeastMatch[3] ?? 0);
25192
+ if (actualMajor !== targetMajor) return actualMajor > targetMajor;
25193
+ if (actualMinor !== targetMinor) return actualMinor > targetMinor;
25194
+ return actualPatch >= targetPatch;
25195
+ }
25196
+ return false;
25197
+ });
25198
+ };
25199
+ /**
25200
+ * Extract "X.Y[.Z]" from arbitrary output. ASCII digits survive any codepage,
25201
+ * so this parses correctly even when surrounding text is mojibake — which is
25202
+ * why probing `node cli.js --version` directly is reliable where a PATH shim
25203
+ * through cmd.exe was not.
25204
+ */
25205
+ const extractVersion = (output) => {
25206
+ const match = /\d+\.\d+(?:\.\d+)?/.exec(output);
25207
+ return match ? match[0] : null;
25208
+ };
25209
+
25210
+ //#endregion
25211
+ //#region src/ru-fork/preflight/common/diagnostics.ts
25212
+ const POSIX_ENV_KEYS = [
25213
+ "HOME",
25214
+ "NODE_PATH",
25215
+ "TRY_TO_FIND_CLI"
25216
+ ];
25217
+ const WINDOWS_ENV_KEYS = [
25218
+ "MSYSTEM",
25219
+ "USERPROFILE",
25220
+ "HOME",
25221
+ "HOMEDRIVE",
25222
+ "HOMEPATH",
25223
+ "APPDATA",
25224
+ "LOCALAPPDATA",
25225
+ "NODE_PATH",
25226
+ "TRY_TO_FIND_CLI"
25227
+ ];
25228
+ const envKeys = () => process.platform === "win32" ? WINDOWS_ENV_KEYS : POSIX_ENV_KEYS;
25229
+ const envValue = (name) => process.env[name] ?? "(unset)";
25230
+ const collectDiagnostics = () => {
25231
+ let username = "?";
25232
+ try {
25233
+ username = NodeOS.userInfo().username;
25234
+ } catch {}
25235
+ return [
25236
+ `platform : ${process.platform} ${NodeOS.release()}`,
25237
+ `version : ${NodeOS.version()}`,
25238
+ `arch : ${process.arch}`,
25239
+ `node : v${process.versions.node} (${process.execPath})`,
25240
+ `home : ${NodeOS.homedir()}`,
25241
+ `user : ${username}`,
25242
+ `cwd : ${process.cwd()}`,
25243
+ ...envKeys().map((name) => `env ${name} = ${envValue(name)}`),
25244
+ `PATH : ${envValue("PATH")}`
25245
+ ];
25246
+ };
25247
+
25248
+ //#endregion
25249
+ //#region src/ru-fork/preflight/common/expand.ts
25250
+ const expand = (pattern, env = process.env) => {
25251
+ const replaced = pattern.replace(/\{home\}/g, NodeOS.homedir()).replace(/\{appdata\}/g, env.APPDATA ?? "").replace(/\{localappdata\}/g, env.LOCALAPPDATA ?? "");
25252
+ return path$1.normalize(replaced);
25253
+ };
25254
+
25255
+ //#endregion
25256
+ //#region src/ru-fork/preflight/common/fs.ts
25257
+ const isDir = (p) => {
25258
+ try {
25259
+ return NFS.statSync(p).isDirectory();
25260
+ } catch {
25261
+ return false;
25262
+ }
25263
+ };
25264
+ const isFile = (p) => {
25265
+ try {
25266
+ return NFS.statSync(p).isFile();
25267
+ } catch {
25268
+ return false;
25269
+ }
25270
+ };
25271
+ /** Read the bin path recorded in `<configDir>/.install-dir`, or "" if absent. */
25272
+ const readInstallRecord = (configDir) => {
25273
+ const file = path$1.join(configDir, ".install-dir");
25274
+ try {
25275
+ return NFS.readFileSync(file, "utf8").replace(/\r/g, "").trim();
25276
+ } catch {
25277
+ return "";
25278
+ }
25279
+ };
25280
+
25281
+ //#endregion
25282
+ //#region src/ru-fork/preflight/common/probe.ts
25283
+ const probeVersion = (command, args, timeoutMs) => {
25284
+ const probe = spawnSync(command, [...args], {
25285
+ timeout: timeoutMs,
25286
+ encoding: "utf8",
25287
+ windowsHide: true
25288
+ });
25289
+ if (probe.error) {
25290
+ const errorCode = probe.error.code;
25291
+ if (errorCode === "ENOENT") return {
25292
+ ok: false,
25293
+ reason: "missing"
25294
+ };
25295
+ if (errorCode === "ETIMEDOUT") return {
25296
+ ok: false,
25297
+ reason: "timeout"
25298
+ };
25299
+ return {
25300
+ ok: false,
25301
+ reason: "broken"
25302
+ };
25303
+ }
25304
+ if (probe.status === null && probe.signal) return {
25305
+ ok: false,
25306
+ reason: "timeout"
25307
+ };
25308
+ if (probe.status !== 0) return {
25309
+ ok: false,
25310
+ reason: "broken"
25311
+ };
25312
+ const version = extractVersion(`${probe.stdout ?? ""}${probe.stderr ?? ""}`);
25313
+ return version ? {
25314
+ ok: true,
25315
+ version
25316
+ } : {
25317
+ ok: false,
25318
+ reason: "broken"
25319
+ };
25320
+ };
25321
+
25322
+ //#endregion
25323
+ //#region src/ru-fork/preflight/paths.ts
25324
+ /**
25325
+ * WHERE the CLI keeps its config dir — always `{home}/$CLI_DIR`, identical
25326
+ * across platforms. First `isDir()` match wins. Any alternative runtime/bin
25327
+ * location is NEVER searched here — it is read from the CLI's own `.install-dir`
25328
+ * record inside the config dir.
25329
+ */
25330
+ const CONFIG = {
25331
+ darwin: [`{home}/${CLI_CONFIG_DIRNAME}`],
25332
+ linux: [`{home}/${CLI_CONFIG_DIRNAME}`],
25333
+ win32: [`{home}/${CLI_CONFIG_DIRNAME}`]
25334
+ };
25335
+ /**
25336
+ * FALLBACK bin probe — for CLI installers that drop `cli.js` outside
25337
+ * `$CLI_DIR/bin`. OFF in production; enabled via the `TRY_TO_FIND_CLI` env flag
25338
+ * for local testing. Each entry is probed for an existing `cli.js`.
25339
+ *
25340
+ * The `<cli>` segments are placeholders — fill them with your real local test
25341
+ * layout. With them unfilled, nothing resolves (so production, where the flag
25342
+ * is unset, can only use the two authoritative sources).
25343
+ */
25344
+ const CLI_BIN_PATHS = {
25345
+ darwin: [
25346
+ "{home}/Library/pnpm/global/5/node_modules/@qwen-code/qwen-code/cli.js",
25347
+ "{home}/.npm-global/lib/node_modules/<cli>/cli.js",
25348
+ "/opt/homebrew/lib/node_modules/<cli>/cli.js"
25349
+ ],
25350
+ linux: ["{home}/.local/share/pnpm/global/5/node_modules/<cli>/cli.js", "/usr/local/lib/node_modules/<cli>/cli.js"],
25351
+ win32: ["{appdata}/npm/node_modules/<cli>/cli.js", "{localappdata}/<cli>/cli.js"]
25352
+ };
25353
+
25354
+ //#endregion
25355
+ //#region src/ru-fork/preflight/common/resolve.ts
25356
+ const toPlatformKey = (platform) => platform === "darwin" ? "darwin" : platform === "win32" ? "win32" : "linux";
25357
+ /**
25358
+ * Step 3 → FALLBACK: probe CLI_BIN_PATHS. Returns the resolved cli.js, or the
25359
+ * list of probed paths (when nothing matched) so the caller can STOP + report.
25360
+ */
25361
+ const tryFallbackBinPaths = (platformKey, env, tryFindCli) => {
25362
+ if (!tryFindCli) return { stop: ["TRY_TO_FIND_CLI выключен"] };
25363
+ const patterns = CLI_BIN_PATHS[platformKey] ?? [];
25364
+ if (patterns.length === 0) return { stop: ["CLI_BIN_PATHS пуст"] };
25365
+ const probedPaths = [];
25366
+ for (const pattern of patterns) {
25367
+ const candidate = expand(pattern, env);
25368
+ probedPaths.push(candidate);
25369
+ if (isFile(candidate)) return { cliJs: candidate };
25370
+ }
25371
+ return { stop: probedPaths };
25372
+ };
25373
+ const resolveCli = (options = {}) => {
25374
+ const platform = options.platform ?? process.platform;
25375
+ const env = options.env ?? process.env;
25376
+ const tryFindCli = options.tryFindCli ?? false;
25377
+ const platformKey = toPlatformKey(platform);
25378
+ const checkedConfigPaths = [];
25379
+ let configDir;
25380
+ for (const pattern of CONFIG[platformKey] ?? []) {
25381
+ const candidate = expand(pattern, env);
25382
+ checkedConfigPaths.push(candidate);
25383
+ if (isDir(candidate)) {
25384
+ configDir = candidate;
25385
+ break;
25386
+ }
25387
+ }
25388
+ if (!configDir) return {
25389
+ ok: false,
25390
+ reason: MESSAGES.CONFIG_NOT_FOUND,
25391
+ details: checkedConfigPaths
25392
+ };
25393
+ const homeBinCli = path$1.join(configDir, "bin", "cli.js");
25394
+ const recordedBinDir = readInstallRecord(configDir);
25395
+ const recordedCli = recordedBinDir ? path$1.join(recordedBinDir, "cli.js") : "";
25396
+ const resolveStandard = () => ({
25397
+ ok: true,
25398
+ configDir,
25399
+ cliJs: homeBinCli,
25400
+ source: "standard",
25401
+ ourRoot: path$1.join(path$1.dirname(configDir), APP_DIR)
25402
+ });
25403
+ const resolveFromInstallRecord = () => ({
25404
+ ok: true,
25405
+ configDir,
25406
+ cliJs: recordedCli,
25407
+ source: "install-dir",
25408
+ ourRoot: path$1.join(path$1.dirname(path$1.dirname(recordedBinDir)), APP_DIR)
25409
+ });
25410
+ const resolveFromFallback = () => {
25411
+ const fallback = tryFallbackBinPaths(platformKey, env, tryFindCli);
25412
+ if ("cliJs" in fallback) return {
25413
+ ok: true,
25414
+ configDir,
25415
+ cliJs: fallback.cliJs,
25416
+ source: "fallback",
25417
+ ourRoot: path$1.join(NodeOS.homedir(), APP_DIR)
25418
+ };
25419
+ return {
25420
+ ok: false,
25421
+ reason: MESSAGES.CLI_NOT_FOUND,
25422
+ details: fallback.stop,
25423
+ configDir
25424
+ };
25425
+ };
25426
+ if (isFile(homeBinCli)) {
25427
+ if (recordedCli && path$1.normalize(recordedCli) !== path$1.normalize(homeBinCli)) return {
25428
+ ok: false,
25429
+ reason: MESSAGES.SOURCES_DISAGREE,
25430
+ details: [`bin: ${homeBinCli}`, `.install-dir: ${recordedCli}`],
25431
+ configDir
25432
+ };
25433
+ return resolveStandard();
25434
+ }
25435
+ if (recordedBinDir) {
25436
+ if (!isFile(recordedCli)) return {
25437
+ ok: false,
25438
+ reason: MESSAGES.INSTALL_DIR_NOWHERE,
25439
+ details: [`.install-dir → ${recordedBinDir}`],
25440
+ configDir
25441
+ };
25442
+ return path$1.basename(recordedBinDir) === "bin" && path$1.basename(path$1.dirname(recordedBinDir)) === CLI_DIR ? resolveFromInstallRecord() : resolveFromFallback();
25443
+ }
25444
+ return resolveFromFallback();
25445
+ };
25446
+
25447
+ //#endregion
25448
+ //#region src/ru-fork/preflight/common/checks.ts
25449
+ /** Node cannot be "missing" here — we are running on it. Validate the engine
25450
+ * range against the process's own version. */
25451
+ const checkNodeEngine = () => {
25452
+ const found = `v${process.versions.node}`;
25453
+ if (!satisfiesRange(process.versions.node, NODE_ENGINE_RANGE)) return {
25454
+ ok: false,
25455
+ line: render(MESSAGES.NODE_LOW, { found })
25456
+ };
25457
+ return {
25458
+ ok: true,
25459
+ line: render(MESSAGES.NODE_OK, { found })
25460
+ };
25461
+ };
25462
+ const checkGit = () => {
25463
+ const gitProbe = probeVersion("git", ["--version"], GIT_PROBE_TIMEOUT_MS);
25464
+ if (!gitProbe.ok) return {
25465
+ ok: false,
25466
+ line: gitProbe.reason === "missing" ? MESSAGES.GIT_MISSING : MESSAGES.GIT_BROKEN
25467
+ };
25468
+ return {
25469
+ ok: true,
25470
+ line: render(MESSAGES.GIT_OK, { found: gitProbe.version })
25471
+ };
25472
+ };
25473
+ /** Probe the resolved cli.js directly with the running node interpreter. */
25474
+ const checkCli = (cliJs) => {
25475
+ const cliProbe = probeVersion(process.execPath, [cliJs, "--version"], CLI_PROBE_TIMEOUT_MS);
25476
+ if (!cliProbe.ok) return {
25477
+ ok: false,
25478
+ line: MESSAGES.CLI_BROKEN
25479
+ };
25480
+ if (CLI_MIN_VERSION && !isAtLeast(cliProbe.version, CLI_MIN_VERSION)) return {
25481
+ ok: false,
25482
+ line: render(MESSAGES.CLI_LOW, { found: cliProbe.version })
25483
+ };
25484
+ return {
25485
+ ok: true,
25486
+ line: render(MESSAGES.CLI_OK, { found: cliProbe.version })
25487
+ };
25488
+ };
25489
+
25490
+ //#endregion
25491
+ //#region src/ru-fork/preflight/preflight-startup.ts
25492
+ /**
25493
+ * Startup preflight — the launch-time twin of the installer preflight.
25494
+ *
25495
+ * Runs the SAME shared resolver + checks as `preflight-install.ts` (the `common/`
25496
+ * core), so install-time and launch-time agree on cli.js / config dir / app root.
25497
+ * This is the whole point of the design: one deterministic resolver, run by the
25498
+ * installer AND the app, so the two never diverge (see
25499
+ * `ru-fork-instrumental/changes/common-preflight.md`).
25500
+ *
25501
+ * Two entry points, because the daemon launcher runs the checks in the PARENT
25502
+ * and spawns the child with `--no-preflight-check`:
25503
+ *
25504
+ * - {@link resolveStartupCli} — ALWAYS run. Resolves cli.js / config dir /
25505
+ * app root via the §10 state machine. The app cannot function without a
25506
+ * cli.js, so a failed resolution STOPs startup with a readable Russian
25507
+ * report (exactly like the installer) and a `PreflightFailedError`. Its
25508
+ * result is threaded into ServerConfig (base dir, CLI config dir) and the
25509
+ * direct-node CLI spawn (cli.js path).
25510
+ *
25511
+ * - {@link runStartupChecks} — gated by `--no-preflight-check`. node engine +
25512
+ * git + `node cli.js --version`, identical to the installer's steps 4-6.
25513
+ *
25514
+ * No shell / Git-Bash / terminal check: the CLI is spawned via `node cli.js`
25515
+ * directly (never bash / cmd / PowerShell), so the launch shell is irrelevant.
25516
+ */
25517
+ var PreflightFailedError = class extends TaggedError("PreflightFailedError") {};
25518
+ const tryFindCliEnabled = () => process.env.TRY_TO_FIND_CLI !== "0";
25519
+ /**
25520
+ * Resolve cli.js / config dir / app root. Always runs (the daemon child skips
25521
+ * the version checks but still needs these paths). STOPs on a failed resolution.
25522
+ */
25523
+ const resolveStartupCli = gen(function* () {
25524
+ for (const line of collectDiagnostics()) yield* logInfo(line);
25525
+ const resolution = resolveCli({ tryFindCli: tryFindCliEnabled() });
25526
+ if (!resolution.ok) {
25527
+ yield* logError(resolution.reason);
25528
+ for (const detail of resolution.details) yield* logError(` ${detail}`);
25529
+ yield* logError(MESSAGES.FOOTER_FAIL);
25530
+ return yield* new PreflightFailedError({ failures: [resolution.reason, ...resolution.details] });
25531
+ }
25532
+ yield* logInfo(`CLI config dir : ${resolution.configDir}`);
25533
+ yield* logInfo(`CLI bin (cli.js): ${resolution.cliJs} [source: ${resolution.source}]`);
25534
+ yield* logInfo(`app root : ${resolution.ourRoot}`);
25535
+ return {
25536
+ cliJs: resolution.cliJs,
25537
+ configDir: resolution.configDir,
25538
+ ourRoot: resolution.ourRoot,
25539
+ source: resolution.source
25540
+ };
25541
+ });
25542
+ /**
25543
+ * node engine + git + `node cli.js --version`. Gated by `--no-preflight-check`.
25544
+ * Aggregates failures and STOPs with a `PreflightFailedError` if any check fails
25545
+ * — identical behaviour to the installer's aggregated steps 4-6.
25546
+ *
25547
+ * The probes are synchronous (`spawnSync` via the shared `probe.ts`); acceptable
25548
+ * here because this runs once at startup, before the server begins serving.
25549
+ */
25550
+ const runStartupChecks = (cliJs) => gen(function* () {
25551
+ const results = [
25552
+ checkNodeEngine(),
25553
+ checkGit(),
25554
+ checkCli(cliJs)
25555
+ ];
25556
+ for (const result of results) if (result.ok) yield* logInfo(result.line);
25557
+ else yield* logError(result.line);
25558
+ const failures = results.filter((result) => !result.ok).map((result) => result.line);
25559
+ if (failures.length > 0) {
25560
+ yield* logError(MESSAGES.FOOTER_FAIL);
25561
+ return yield* new PreflightFailedError({ failures });
25562
+ }
25563
+ });
25564
+
25121
25565
  //#endregion
25122
25566
  //#region src/ru-fork/local-startup/defaults.ts
25123
25567
  const DESKTOP_RUNTIME_MODE = "desktop";
@@ -26139,7 +26583,7 @@ const sharedServerCommandFlags = {
26139
26583
  };
26140
26584
  const authLocationFlags = sharedServerLocationFlags;
26141
26585
  const resolveOptionPrecedence = (...values) => firstSomeOf(values);
26142
- const resolveServerConfig = (flags, cliLogLevel, options) => gen(function* () {
26586
+ const resolveServerConfig = (flags, cliLogLevel, cli, options) => gen(function* () {
26143
26587
  const { findAvailablePort } = yield* NetService;
26144
26588
  const path = yield* Path;
26145
26589
  const fs = yield* FileSystem;
@@ -26173,7 +26617,7 @@ const resolveServerConfig = (flags, cliLogLevel, options) => gen(function* () {
26173
26617
  });
26174
26618
  const devUrl = getOrElse(resolveOptionPrecedence(normalizedFlags.devUrl, fromUndefinedOr(env.devUrl)), () => void 0);
26175
26619
  const basePath = normalizeBasePath(getOrUndefined(resolveOptionPrecedence(normalizedFlags.baseUrl, fromUndefinedOr(env.baseUrl))));
26176
- const baseDir = yield* resolveBaseDir(getOrUndefined(resolveOptionPrecedence(normalizedFlags.baseDir, fromUndefinedOr(env.t3Home), fromUndefinedOr(bootstrap?.t3Home))));
26620
+ const baseDir = yield* resolveBaseDir(getOrUndefined(resolveOptionPrecedence(normalizedFlags.baseDir, fromUndefinedOr(env.t3Home), fromUndefinedOr(bootstrap?.t3Home))) ?? cli.ourRoot);
26177
26621
  const rawCwd = getOrElse(normalizedFlags.cwd, () => process.cwd());
26178
26622
  const cwd = path.resolve(yield* expandHomePath$4(rawCwd.trim()));
26179
26623
  yield* fs.makeDirectory(cwd, { recursive: true });
@@ -26193,6 +26637,8 @@ const resolveServerConfig = (flags, cliLogLevel, options) => gen(function* () {
26193
26637
  port,
26194
26638
  cwd,
26195
26639
  baseDir,
26640
+ cliJs: cli.cliJs,
26641
+ cliConfigDir: cli.configDir,
26196
26642
  ...derivedPaths,
26197
26643
  host,
26198
26644
  staticDir,
@@ -26205,22 +26651,25 @@ const resolveServerConfig = (flags, cliLogLevel, options) => gen(function* () {
26205
26651
  basePath
26206
26652
  };
26207
26653
  });
26208
- const resolveCliAuthConfig = (flags, cliLogLevel) => resolveServerConfig({
26209
- mode: none$4(),
26210
- port: none$4(),
26211
- host: none$4(),
26212
- baseDir: flags.baseDir,
26213
- cwd: none$4(),
26214
- devUrl: flags.devUrl ?? none$4(),
26215
- baseUrl: flags.baseUrl ?? none$4(),
26216
- noBrowser: none$4(),
26217
- noPreflightCheck: none$4(),
26218
- bootstrapFd: none$4(),
26219
- autoBootstrapProjectFromCwd: none$4(),
26220
- logWebSocketEvents: none$4(),
26221
- injectExtraPaths: none$4(),
26222
- windowsUseBashFor: none$4()
26223
- }, cliLogLevel);
26654
+ const resolveCliAuthConfig = (flags, cliLogLevel) => gen(function* () {
26655
+ const cli = yield* resolveStartupCli;
26656
+ return yield* resolveServerConfig({
26657
+ mode: none$4(),
26658
+ port: none$4(),
26659
+ host: none$4(),
26660
+ baseDir: flags.baseDir,
26661
+ cwd: none$4(),
26662
+ devUrl: flags.devUrl ?? none$4(),
26663
+ baseUrl: flags.baseUrl ?? none$4(),
26664
+ noBrowser: none$4(),
26665
+ noPreflightCheck: none$4(),
26666
+ bootstrapFd: none$4(),
26667
+ autoBootstrapProjectFromCwd: none$4(),
26668
+ logWebSocketEvents: none$4(),
26669
+ injectExtraPaths: none$4(),
26670
+ windowsUseBashFor: none$4()
26671
+ }, cliLogLevel, cli);
26672
+ });
26224
26673
  const DurationShorthandPattern = /^(?<value>\d+)(?<unit>ms|s|m|h|d|w)$/i;
26225
26674
  const parseDurationInput = (value) => {
26226
26675
  const trimmed = value.trim();
@@ -31698,6 +32147,11 @@ const initSpawnPolicy = (input) => {
31698
32147
  if (prepend.length === 0) return;
31699
32148
  process.env.PATH = [...prepend, process.env.PATH ?? ""].filter(Boolean).join(isWin ? ";" : ":");
31700
32149
  };
32150
+ const buildCliSpawn = (cliJs, args) => ({
32151
+ command: process.execPath,
32152
+ args: [cliJs, ...args],
32153
+ shell: false
32154
+ });
31701
32155
  const resolveSpawn = (bin, args, options) => {
31702
32156
  if (process.platform !== "win32") return {
31703
32157
  command: bin,
@@ -34779,523 +35233,6 @@ const projectCommand = make$35("project").pipe(withDescription$1("Manage project
34779
35233
  projectRenameCommand
34780
35234
  ]));
34781
35235
 
34782
- //#endregion
34783
- //#region ../../packages/shared/src/semver.ts
34784
- const SEMVER_NUMBER_SEGMENT = /^\d+$/;
34785
- function normalizeSemverVersion(version) {
34786
- const [main, prerelease] = version.trim().split("-", 2);
34787
- const segments = (main ?? "").split(".").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
34788
- if (segments.length === 2) segments.push("0");
34789
- return prerelease ? `${segments.join(".")}-${prerelease}` : segments.join(".");
34790
- }
34791
- function parseSemver(value) {
34792
- const [main = "", prerelease] = normalizeSemverVersion(value).replace(/^v/, "").split("-", 2);
34793
- const segments = main.split(".");
34794
- if (segments.length !== 3) return null;
34795
- const [majorSegment, minorSegment, patchSegment] = segments;
34796
- if (majorSegment === void 0 || minorSegment === void 0 || patchSegment === void 0) return null;
34797
- if (!SEMVER_NUMBER_SEGMENT.test(majorSegment) || !SEMVER_NUMBER_SEGMENT.test(minorSegment) || !SEMVER_NUMBER_SEGMENT.test(patchSegment)) return null;
34798
- const major = Number.parseInt(majorSegment, 10);
34799
- const minor = Number.parseInt(minorSegment, 10);
34800
- const patch = Number.parseInt(patchSegment, 10);
34801
- if (![
34802
- major,
34803
- minor,
34804
- patch
34805
- ].every(Number.isInteger)) return null;
34806
- return {
34807
- major,
34808
- minor,
34809
- patch,
34810
- prerelease: prerelease?.split(".").map((segment) => segment.trim()).filter((segment) => segment.length > 0) ?? []
34811
- };
34812
- }
34813
- function comparePrereleaseIdentifier(left, right) {
34814
- const leftNumeric = SEMVER_NUMBER_SEGMENT.test(left);
34815
- const rightNumeric = SEMVER_NUMBER_SEGMENT.test(right);
34816
- if (leftNumeric && rightNumeric) return Number.parseInt(left, 10) - Number.parseInt(right, 10);
34817
- if (leftNumeric) return -1;
34818
- if (rightNumeric) return 1;
34819
- return left.localeCompare(right);
34820
- }
34821
- function compareSemverVersions(left, right) {
34822
- const parsedLeft = parseSemver(left);
34823
- const parsedRight = parseSemver(right);
34824
- if (!parsedLeft || !parsedRight) return left.localeCompare(right);
34825
- if (parsedLeft.major !== parsedRight.major) return parsedLeft.major - parsedRight.major;
34826
- if (parsedLeft.minor !== parsedRight.minor) return parsedLeft.minor - parsedRight.minor;
34827
- if (parsedLeft.patch !== parsedRight.patch) return parsedLeft.patch - parsedRight.patch;
34828
- if (parsedLeft.prerelease.length === 0 && parsedRight.prerelease.length === 0) return 0;
34829
- if (parsedLeft.prerelease.length === 0) return 1;
34830
- if (parsedRight.prerelease.length === 0) return -1;
34831
- const length = Math.max(parsedLeft.prerelease.length, parsedRight.prerelease.length);
34832
- for (let index = 0; index < length; index += 1) {
34833
- const leftIdentifier = parsedLeft.prerelease[index];
34834
- const rightIdentifier = parsedRight.prerelease[index];
34835
- if (leftIdentifier === void 0) return -1;
34836
- if (rightIdentifier === void 0) return 1;
34837
- const comparison = comparePrereleaseIdentifier(leftIdentifier, rightIdentifier);
34838
- if (comparison !== 0) return comparison;
34839
- }
34840
- return 0;
34841
- }
34842
-
34843
- //#endregion
34844
- //#region src/provider/providerMaintenance.ts
34845
- const LATEST_VERSION_CACHE_TTL_MS = 3600 * 1e3;
34846
- const LATEST_VERSION_TIMEOUT_MS = 4e3;
34847
- const PROVIDER_UPDATE_ACTION_TOAST_MESSAGE = "Install the update now or review provider settings.";
34848
- const latestVersionCache = /* @__PURE__ */ new Map();
34849
- const NpmLatestVersionResponse = Struct({ version: optional$3(String$1) });
34850
- function nonEmptyString(value) {
34851
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
34852
- }
34853
- function makeProviderMaintenanceCapabilities(input) {
34854
- const update = input.updateExecutable === null || input.updateLockKey === null ? null : {
34855
- command: [input.updateExecutable, ...input.updateArgs].join(" "),
34856
- executable: input.updateExecutable,
34857
- args: input.updateArgs,
34858
- lockKey: input.updateLockKey
34859
- };
34860
- return {
34861
- provider: input.provider,
34862
- packageName: input.packageName,
34863
- update
34864
- };
34865
- }
34866
- function makeManualOnlyProviderMaintenanceCapabilities(input) {
34867
- return makeProviderMaintenanceCapabilities({
34868
- provider: input.provider,
34869
- packageName: input.packageName,
34870
- updateExecutable: null,
34871
- updateArgs: [],
34872
- updateLockKey: null
34873
- });
34874
- }
34875
- function hasPathSeparator(value) {
34876
- return value.includes("/") || value.includes("\\");
34877
- }
34878
- function makeStaticProviderMaintenanceResolver(capabilities) {
34879
- return { resolve: () => capabilities };
34880
- }
34881
- function makeManualProviderMaintenanceCapabilities$1(provider) {
34882
- return makeManualOnlyProviderMaintenanceCapabilities({
34883
- provider,
34884
- packageName: null
34885
- });
34886
- }
34887
- const resolveProviderMaintenanceCapabilitiesEffect = fn("resolveProviderMaintenanceCapabilitiesEffect")(function* (resolver, options) {
34888
- const binaryPath = nonEmptyString(options?.binaryPath);
34889
- if (!binaryPath) return resolver.resolve(options);
34890
- const resolvedCommandPath = resolveCommandPath(binaryPath, {
34891
- ...options?.platform ? { platform: options.platform } : {},
34892
- ...options?.env ? { env: options.env } : {}
34893
- }) ?? (hasPathSeparator(binaryPath) ? binaryPath : null);
34894
- if (!resolvedCommandPath) return resolver.resolve(options);
34895
- const realCommandPath = yield* (yield* FileSystem).realPath(resolvedCommandPath).pipe(catch_(() => succeed(resolvedCommandPath)));
34896
- return resolver.resolve({
34897
- ...options,
34898
- realCommandPath
34899
- });
34900
- });
34901
- function deriveVersionAdvisory(input) {
34902
- if (!input.currentVersion) return {
34903
- status: "unknown",
34904
- message: null
34905
- };
34906
- if (!input.latestVersion) return {
34907
- status: "unknown",
34908
- message: null
34909
- };
34910
- if (compareSemverVersions(input.currentVersion, input.latestVersion) < 0) return {
34911
- status: "behind_latest",
34912
- message: PROVIDER_UPDATE_ACTION_TOAST_MESSAGE
34913
- };
34914
- return {
34915
- status: "current",
34916
- message: null
34917
- };
34918
- }
34919
- function createProviderVersionAdvisory(input) {
34920
- const capabilities = input.maintenanceCapabilities ?? makeManualProviderMaintenanceCapabilities$1(input.driver);
34921
- const latestVersion = input.latestVersion ?? null;
34922
- const advisory = deriveVersionAdvisory({
34923
- currentVersion: input.currentVersion,
34924
- latestVersion
34925
- });
34926
- return {
34927
- status: advisory.status,
34928
- currentVersion: input.currentVersion,
34929
- latestVersion,
34930
- updateCommand: capabilities.update?.command ?? null,
34931
- canUpdate: capabilities.update !== null,
34932
- checkedAt: input.checkedAt ?? null,
34933
- message: advisory.message
34934
- };
34935
- }
34936
- const fetchNpmLatestVersion = fn("fetchNpmLatestVersion")(function* (packageName) {
34937
- const client = yield* HttpClient;
34938
- const request = get$7(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`).pipe(setHeader("accept", "application/json"));
34939
- const response = yield* client.execute(request).pipe(timeoutOption(LATEST_VERSION_TIMEOUT_MS), catch_(() => succeed(none$4())));
34940
- if (isNone(response)) return null;
34941
- const httpResponse = response.value;
34942
- if (httpResponse.status < 200 || httpResponse.status >= 300) return null;
34943
- const payload = yield* httpResponse.json.pipe(flatMap(decodeUnknownEffect(NpmLatestVersionResponse)), catch_(() => succeed(null)));
34944
- return payload ? nonEmptyString(payload.version) : null;
34945
- });
34946
- const resolveLatestProviderVersion = fn("resolveLatestProviderVersion")(function* (maintenanceCapabilities) {
34947
- const packageName = maintenanceCapabilities.packageName;
34948
- if (!packageName) return null;
34949
- const cached = latestVersionCache.get(packageName);
34950
- const now$6 = toEpochMillis(yield* now);
34951
- if (cached && cached.expiresAt > now$6) return cached.version;
34952
- const version = yield* fetchNpmLatestVersion(packageName);
34953
- latestVersionCache.set(packageName, {
34954
- expiresAt: now$6 + LATEST_VERSION_CACHE_TTL_MS,
34955
- version
34956
- });
34957
- return version;
34958
- });
34959
- const enrichProviderSnapshotWithVersionAdvisory = fn("enrichProviderSnapshotWithVersionAdvisory")(function* (snapshot, maintenanceCapabilities) {
34960
- const capabilities = maintenanceCapabilities ?? makeManualProviderMaintenanceCapabilities$1(snapshot.driver);
34961
- if (!snapshot.enabled || !snapshot.installed || !snapshot.version) return {
34962
- ...snapshot,
34963
- versionAdvisory: createProviderVersionAdvisory({
34964
- driver: snapshot.driver,
34965
- currentVersion: snapshot.version,
34966
- checkedAt: snapshot.checkedAt,
34967
- maintenanceCapabilities: capabilities
34968
- })
34969
- };
34970
- const latestVersion = yield* resolveLatestProviderVersion(capabilities);
34971
- return {
34972
- ...snapshot,
34973
- versionAdvisory: createProviderVersionAdvisory({
34974
- driver: snapshot.driver,
34975
- currentVersion: snapshot.version,
34976
- latestVersion,
34977
- checkedAt: formatIso(yield* now),
34978
- maintenanceCapabilities: capabilities
34979
- })
34980
- };
34981
- });
34982
-
34983
- //#endregion
34984
- //#region src/stream/collectUint8StreamText.ts
34985
- const collectUint8StreamText = (input) => {
34986
- const decoder = new TextDecoder();
34987
- const maxBytes = input.maxBytes ?? Number.POSITIVE_INFINITY;
34988
- const truncatedMarker = input.truncatedMarker ?? "";
34989
- return input.stream.pipe(runFold(() => ({
34990
- text: "",
34991
- bytes: 0,
34992
- truncated: false
34993
- }), (state, chunk) => {
34994
- if (state.truncated) return state;
34995
- const remainingBytes = maxBytes - state.bytes;
34996
- if (remainingBytes <= 0) return {
34997
- ...state,
34998
- text: `${state.text}${truncatedMarker}`,
34999
- truncated: true
35000
- };
35001
- const nextChunk = chunk.byteLength > remainingBytes ? chunk.slice(0, remainingBytes) : chunk;
35002
- const text = `${state.text}${decoder.decode(nextChunk, { stream: true })}`;
35003
- const bytes = state.bytes + nextChunk.byteLength;
35004
- const truncated = chunk.byteLength > remainingBytes;
35005
- return {
35006
- text: truncated ? `${text}${truncatedMarker}` : text,
35007
- bytes,
35008
- truncated
35009
- };
35010
- }), map$3((state) => ({
35011
- text: state.truncated ? state.text : `${state.text}${decoder.decode()}`,
35012
- bytes: state.bytes,
35013
- truncated: state.truncated
35014
- })));
35015
- };
35016
-
35017
- //#endregion
35018
- //#region src/provider/providerSnapshot.ts
35019
- function isCommandMissingCause(error) {
35020
- const lower = error.message.toLowerCase();
35021
- return lower.includes("enoent") || lower.includes("notfound");
35022
- }
35023
- function parseGenericCliVersion(output) {
35024
- return output.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
35025
- }
35026
- function buildServerProvider(input) {
35027
- const versionAdvisory = input.driver ? createProviderVersionAdvisory({
35028
- driver: input.driver,
35029
- currentVersion: input.probe.version,
35030
- checkedAt: input.checkedAt
35031
- }) : void 0;
35032
- return {
35033
- displayName: input.presentation.displayName,
35034
- ...input.presentation.badgeLabel ? { badgeLabel: input.presentation.badgeLabel } : {},
35035
- ...typeof input.presentation.showInteractionModeToggle === "boolean" ? { showInteractionModeToggle: input.presentation.showInteractionModeToggle } : {},
35036
- enabled: input.enabled,
35037
- installed: input.probe.installed,
35038
- version: input.probe.version,
35039
- status: input.enabled ? input.probe.status : "disabled",
35040
- auth: input.probe.auth,
35041
- checkedAt: input.checkedAt,
35042
- ...input.probe.message ? { message: input.probe.message } : {},
35043
- models: input.models,
35044
- slashCommands: [...input.slashCommands ?? []],
35045
- skills: [...input.skills ?? []],
35046
- ...versionAdvisory ? { versionAdvisory } : {}
35047
- };
35048
- }
35049
- const collectStreamAsString = (stream) => collectUint8StreamText({ stream }).pipe(map$3((collected) => collected.text));
35050
-
35051
- //#endregion
35052
- //#region src/ru-fork/startup/constants.ts
35053
- /**
35054
- * Startup-gate constants. Mirrored by hand in:
35055
- * - `install` (bash, top-of-file constants block)
35056
- * - `apps/server/package.json` engines.node (NODE_ENGINE_RANGE only)
35057
- * - `package.json` (repo root) engines.node (NODE_ENGINE_RANGE only)
35058
- *
35059
- * Change procedure: update this file and every mirror site in the same
35060
- * commit. Reviewer cross-checks all four.
35061
- */
35062
- const NODE_ENGINE_RANGE = "^22.16 || ^23.11 || >=24.10";
35063
- /**
35064
- * Minimum required CLI version (compared with `isAtLeast`).
35065
- * Set to "" (empty string) to disable the version check and only
35066
- * verify presence.
35067
- */
35068
- const CLI_MIN_VERSION = "0.13.1";
35069
- /**
35070
- * Russian message strings. `{found}` is the only placeholder rendered
35071
- * at use time. `NODE_ENGINE_RANGE`, `CLI_MIN_VERSION`, `CLI_NAME`, and
35072
- * `CLI_BINARY_NAME` are baked in via template literals at module load
35073
- * — same pattern as the bash mirror in `install`, which uses `${VAR}`
35074
- * interpolation at definition time.
35075
- *
35076
- * Note: NODE_MISSING intentionally has no TS counterpart — node cannot
35077
- * be missing in a running node process. The install script's
35078
- * MSG_NODE_MISSING covers the pre-install case.
35079
- */
35080
- const MESSAGES = {
35081
- HEADER: `${APP_NAME}: проверка зависимостей...`,
35082
- NODE_OK: " Node.js {found} ✓",
35083
- NODE_LOW: ` Node.js {found} установлен, требуется ${NODE_ENGINE_RANGE}. Обновите: https://nodejs.org/`,
35084
- GIT_OK: " Git {found} ✓",
35085
- GIT_MISSING: " Git не найден на PATH. Установите Git: https://git-scm.com/downloads",
35086
- GIT_BASH_REQUIRED: ` ${APP_NAME} должен запускаться из Git Bash. Установите Git for Windows: https://git-scm.com/downloads`,
35087
- GIT_BROKEN: " Git установлен, но команда `git --version` завершилась с ошибкой или превысила тайм-аут.",
35088
- CLI_OK: ` ${CLI_NAME} {found} ✓`,
35089
- CLI_MISSING: ` ${CLI_NAME} не найден. Установите: npm install -g ${CLI_NPM_PACKAGE}`,
35090
- CLI_LOW: ` ${CLI_NAME} {found} установлен, требуется ≥ ${CLI_MIN_VERSION}. Обновите: npm install -g ${CLI_NPM_PACKAGE}@latest`,
35091
- CLI_BROKEN: ` ${CLI_NAME} установлен, но команда \`${CLI_BINARY_NAME} --version\` завершилась с ошибкой или превысила тайм-аут.`,
35092
- FOOTER_FAIL: "Установите недостающие компоненты и перезапустите."
35093
- };
35094
-
35095
- //#endregion
35096
- //#region src/ru-fork/startup/versionRange.ts
35097
- const parseVersion = (input) => {
35098
- const m = /(\d+)\.(\d+)(?:\.(\d+))?/.exec(input);
35099
- if (!m) return null;
35100
- return [
35101
- Number(m[1]),
35102
- Number(m[2]),
35103
- Number(m[3] ?? 0)
35104
- ];
35105
- };
35106
- const isAtLeast = (actual, minimum) => {
35107
- const a = parseVersion(actual);
35108
- const b = parseVersion(minimum);
35109
- if (!a || !b) return false;
35110
- const [aMaj, aMin, aPat] = a;
35111
- const [bMaj, bMin, bPat] = b;
35112
- if (aMaj !== bMaj) return aMaj > bMaj;
35113
- if (aMin !== bMin) return aMin > bMin;
35114
- return aPat >= bPat;
35115
- };
35116
- const satisfiesRange = (actual, range) => {
35117
- const parsed = parseVersion(actual);
35118
- if (!parsed) return false;
35119
- const [aMaj, aMin, aPat] = parsed;
35120
- return range.split("||").some((raw) => {
35121
- const disjunct = raw.trim();
35122
- let m = /^\^(\d+)\.(\d+)(?:\.(\d+))?$/.exec(disjunct);
35123
- if (m) return aMaj === Number(m[1]) && aMin >= Number(m[2]);
35124
- m = /^>=(\d+)\.(\d+)(?:\.(\d+))?$/.exec(disjunct);
35125
- if (m) {
35126
- const tMaj = Number(m[1]);
35127
- const tMin = Number(m[2]);
35128
- const tPat = Number(m[3] ?? 0);
35129
- if (aMaj !== tMaj) return aMaj > tMaj;
35130
- if (aMin !== tMin) return aMin > tMin;
35131
- return aPat >= tPat;
35132
- }
35133
- return false;
35134
- });
35135
- };
35136
-
35137
- //#endregion
35138
- //#region src/ru-fork/startup/preflight.ts
35139
- /**
35140
- * Startup-gate preflight Effect.
35141
- *
35142
- * Probes node / git / CLI, aggregates failures, logs each result via
35143
- * `Effect.logInfo` (ok) or `Effect.logError` (miss), and fails with a
35144
- * typed `PreflightFailedError` when any check fails so `Command.run`
35145
- * exits non-zero naturally.
35146
- *
35147
- * Call sites: `runServerCommand` (cli/server.ts) and
35148
- * `runDaemonLauncher` (daemonLauncher.ts), each gated by the
35149
- * `--no-preflight-check` flag.
35150
- *
35151
- * Mirrors install (bash) behaviour. See
35152
- * `ru-fork-instrumental/changes/deamon/startap-checks.md`.
35153
- */
35154
- const GIT_TIMEOUT_MS = 5e3;
35155
- const CLI_TIMEOUT_MS$1 = 15e3;
35156
- var PreflightFailedError = class extends TaggedError("PreflightFailedError") {};
35157
- const render = (template, values) => template.replace(/\{(\w+)\}/g, (_, k) => values[k] ?? `{${k}}`);
35158
- /**
35159
- * Spawns `bin --version` and inspects the outcome. Mirrors the exact
35160
- * pattern in `CliProvider.ts:127-195`: pipe spawn → `Effect.timeoutOption`
35161
- * → `Effect.result`, then branch on the Result/Option shape:
35162
- * - failure (e.g. ENOENT) → "missing" or "broken" via `isCommandMissingCause`
35163
- * - None (timeoutOption returned None) → "timeout"
35164
- * - exitCode !== 0 → "broken"
35165
- * - else → ok with stdout
35166
- *
35167
- * `shell` mirrors the existing per-tool spawn shapes:
35168
- * - git: shell=false (matches `GitVcsDriverCore.ts:642`)
35169
- * - CLI: shell=process.platform==="win32" (matches `CliProvider.ts:135`)
35170
- */
35171
- const probe = (bin, args, timeoutMs, shell) => gen(function* () {
35172
- const resolved = resolveSpawn(bin, args, { shell });
35173
- const probeResult = yield* gen(function* () {
35174
- const spawner = yield* ChildProcessSpawner;
35175
- const command = make$46(resolved.command, [...resolved.args], { shell: resolved.shell });
35176
- const child = yield* spawner.spawn(command);
35177
- const [stdout, stderr, exitCode] = yield* all([
35178
- collectStreamAsString(child.stdout),
35179
- collectStreamAsString(child.stderr),
35180
- child.exitCode.pipe(map$3(Number))
35181
- ], { concurrency: "unbounded" });
35182
- return {
35183
- stdout,
35184
- stderr,
35185
- exitCode
35186
- };
35187
- }).pipe(scoped, timeoutOption(timeoutMs), result);
35188
- if (isFailure$1(probeResult)) return isCommandMissingCause(probeResult.failure) ? {
35189
- ok: false,
35190
- reason: "missing"
35191
- } : {
35192
- ok: false,
35193
- reason: "broken"
35194
- };
35195
- if (isNone(probeResult.success)) return {
35196
- ok: false,
35197
- reason: "timeout"
35198
- };
35199
- const { stdout, stderr, exitCode } = probeResult.success.value;
35200
- return exitCode === 0 ? {
35201
- ok: true,
35202
- stdout
35203
- } : {
35204
- ok: false,
35205
- reason: "broken",
35206
- stderr
35207
- };
35208
- });
35209
- const checkShell = () => sync$1(() => {
35210
- if (process.platform !== "win32") return {
35211
- ok: true,
35212
- line: ""
35213
- };
35214
- if (!process.env.MSYSTEM) return {
35215
- ok: false,
35216
- line: MESSAGES.GIT_BASH_REQUIRED
35217
- };
35218
- return {
35219
- ok: true,
35220
- line: ""
35221
- };
35222
- });
35223
- const checkNode = () => sync$1(() => {
35224
- const current = `v${process.versions.node}`;
35225
- if (!satisfiesRange(process.versions.node, NODE_ENGINE_RANGE)) return {
35226
- ok: false,
35227
- line: render(MESSAGES.NODE_LOW, { found: current })
35228
- };
35229
- return {
35230
- ok: true,
35231
- line: render(MESSAGES.NODE_OK, { found: current })
35232
- };
35233
- });
35234
- const appendDetail = (baseLine, stderr) => {
35235
- const detail = stderr?.trim();
35236
- return detail ? `${baseLine}\n ${detail}` : baseLine;
35237
- };
35238
- const checkGit = () => gen(function* () {
35239
- const result = yield* probe("git", ["--version"], GIT_TIMEOUT_MS, false);
35240
- if (!result.ok) return {
35241
- ok: false,
35242
- line: appendDetail(result.reason === "missing" ? MESSAGES.GIT_MISSING : MESSAGES.GIT_BROKEN, result.stderr)
35243
- };
35244
- const parsed = parseVersion(result.stdout);
35245
- if (!parsed) return {
35246
- ok: false,
35247
- line: MESSAGES.GIT_BROKEN
35248
- };
35249
- return {
35250
- ok: true,
35251
- line: render(MESSAGES.GIT_OK, { found: parsed.join(".") })
35252
- };
35253
- });
35254
- const checkCli = () => gen(function* () {
35255
- const result = yield* probe(CLI_BINARY_NAME, ["--version"], CLI_TIMEOUT_MS$1, process.platform === "win32");
35256
- if (!result.ok) return {
35257
- ok: false,
35258
- line: appendDetail(result.reason === "missing" ? MESSAGES.CLI_MISSING : MESSAGES.CLI_BROKEN, result.stderr)
35259
- };
35260
- const parsed = parseVersion(result.stdout);
35261
- if (!parsed) return {
35262
- ok: false,
35263
- line: MESSAGES.CLI_BROKEN
35264
- };
35265
- const found = parsed.join(".");
35266
- if (CLI_MIN_VERSION && !isAtLeast(found, CLI_MIN_VERSION)) return {
35267
- ok: false,
35268
- line: render(MESSAGES.CLI_LOW, { found })
35269
- };
35270
- return {
35271
- ok: true,
35272
- line: render(MESSAGES.CLI_OK, { found })
35273
- };
35274
- });
35275
- /**
35276
- * Run all three checks, log each result line at info (ok) or error
35277
- * (failure) level, fail with `PreflightFailedError` if any failed.
35278
- */
35279
- const runPreflight = gen(function* () {
35280
- yield* logInfo(MESSAGES.HEADER);
35281
- const results = yield* all([
35282
- checkShell(),
35283
- checkNode(),
35284
- checkGit(),
35285
- checkCli()
35286
- ], { concurrency: "unbounded" });
35287
- for (const r of results) {
35288
- if (r.line.length === 0) continue;
35289
- if (r.ok) yield* logInfo(r.line);
35290
- else yield* logError(r.line);
35291
- }
35292
- const failureLines = results.filter((r) => !r.ok).map((r) => r.line).filter((line) => line.length > 0);
35293
- if (failureLines.length > 0) {
35294
- yield* logError(MESSAGES.FOOTER_FAIL);
35295
- return yield* new PreflightFailedError({ failures: failureLines });
35296
- }
35297
- });
35298
-
35299
35236
  //#endregion
35300
35237
  //#region src/ru-fork/local-startup/assertLocalPortAvailable.ts
35301
35238
  var PortInUseError = class extends TaggedError("PortInUseError") {};
@@ -39871,6 +39808,241 @@ const makeProviderMaintenanceCommandCoordinator = fn("makeProviderMaintenanceCom
39871
39808
  return { withCommandLock };
39872
39809
  });
39873
39810
 
39811
+ //#endregion
39812
+ //#region ../../packages/shared/src/semver.ts
39813
+ const SEMVER_NUMBER_SEGMENT = /^\d+$/;
39814
+ function normalizeSemverVersion(version) {
39815
+ const [main, prerelease] = version.trim().split("-", 2);
39816
+ const segments = (main ?? "").split(".").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
39817
+ if (segments.length === 2) segments.push("0");
39818
+ return prerelease ? `${segments.join(".")}-${prerelease}` : segments.join(".");
39819
+ }
39820
+ function parseSemver(value) {
39821
+ const [main = "", prerelease] = normalizeSemverVersion(value).replace(/^v/, "").split("-", 2);
39822
+ const segments = main.split(".");
39823
+ if (segments.length !== 3) return null;
39824
+ const [majorSegment, minorSegment, patchSegment] = segments;
39825
+ if (majorSegment === void 0 || minorSegment === void 0 || patchSegment === void 0) return null;
39826
+ if (!SEMVER_NUMBER_SEGMENT.test(majorSegment) || !SEMVER_NUMBER_SEGMENT.test(minorSegment) || !SEMVER_NUMBER_SEGMENT.test(patchSegment)) return null;
39827
+ const major = Number.parseInt(majorSegment, 10);
39828
+ const minor = Number.parseInt(minorSegment, 10);
39829
+ const patch = Number.parseInt(patchSegment, 10);
39830
+ if (![
39831
+ major,
39832
+ minor,
39833
+ patch
39834
+ ].every(Number.isInteger)) return null;
39835
+ return {
39836
+ major,
39837
+ minor,
39838
+ patch,
39839
+ prerelease: prerelease?.split(".").map((segment) => segment.trim()).filter((segment) => segment.length > 0) ?? []
39840
+ };
39841
+ }
39842
+ function comparePrereleaseIdentifier(left, right) {
39843
+ const leftNumeric = SEMVER_NUMBER_SEGMENT.test(left);
39844
+ const rightNumeric = SEMVER_NUMBER_SEGMENT.test(right);
39845
+ if (leftNumeric && rightNumeric) return Number.parseInt(left, 10) - Number.parseInt(right, 10);
39846
+ if (leftNumeric) return -1;
39847
+ if (rightNumeric) return 1;
39848
+ return left.localeCompare(right);
39849
+ }
39850
+ function compareSemverVersions(left, right) {
39851
+ const parsedLeft = parseSemver(left);
39852
+ const parsedRight = parseSemver(right);
39853
+ if (!parsedLeft || !parsedRight) return left.localeCompare(right);
39854
+ if (parsedLeft.major !== parsedRight.major) return parsedLeft.major - parsedRight.major;
39855
+ if (parsedLeft.minor !== parsedRight.minor) return parsedLeft.minor - parsedRight.minor;
39856
+ if (parsedLeft.patch !== parsedRight.patch) return parsedLeft.patch - parsedRight.patch;
39857
+ if (parsedLeft.prerelease.length === 0 && parsedRight.prerelease.length === 0) return 0;
39858
+ if (parsedLeft.prerelease.length === 0) return 1;
39859
+ if (parsedRight.prerelease.length === 0) return -1;
39860
+ const length = Math.max(parsedLeft.prerelease.length, parsedRight.prerelease.length);
39861
+ for (let index = 0; index < length; index += 1) {
39862
+ const leftIdentifier = parsedLeft.prerelease[index];
39863
+ const rightIdentifier = parsedRight.prerelease[index];
39864
+ if (leftIdentifier === void 0) return -1;
39865
+ if (rightIdentifier === void 0) return 1;
39866
+ const comparison = comparePrereleaseIdentifier(leftIdentifier, rightIdentifier);
39867
+ if (comparison !== 0) return comparison;
39868
+ }
39869
+ return 0;
39870
+ }
39871
+
39872
+ //#endregion
39873
+ //#region src/provider/providerMaintenance.ts
39874
+ const LATEST_VERSION_CACHE_TTL_MS = 3600 * 1e3;
39875
+ const LATEST_VERSION_TIMEOUT_MS = 4e3;
39876
+ const PROVIDER_UPDATE_ACTION_TOAST_MESSAGE = "Install the update now or review provider settings.";
39877
+ const latestVersionCache = /* @__PURE__ */ new Map();
39878
+ const NpmLatestVersionResponse = Struct({ version: optional$3(String$1) });
39879
+ function nonEmptyString(value) {
39880
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
39881
+ }
39882
+ function makeProviderMaintenanceCapabilities(input) {
39883
+ const update = input.updateExecutable === null || input.updateLockKey === null ? null : {
39884
+ command: [input.updateExecutable, ...input.updateArgs].join(" "),
39885
+ executable: input.updateExecutable,
39886
+ args: input.updateArgs,
39887
+ lockKey: input.updateLockKey
39888
+ };
39889
+ return {
39890
+ provider: input.provider,
39891
+ packageName: input.packageName,
39892
+ update
39893
+ };
39894
+ }
39895
+ function makeManualOnlyProviderMaintenanceCapabilities(input) {
39896
+ return makeProviderMaintenanceCapabilities({
39897
+ provider: input.provider,
39898
+ packageName: input.packageName,
39899
+ updateExecutable: null,
39900
+ updateArgs: [],
39901
+ updateLockKey: null
39902
+ });
39903
+ }
39904
+ function hasPathSeparator(value) {
39905
+ return value.includes("/") || value.includes("\\");
39906
+ }
39907
+ function makeStaticProviderMaintenanceResolver(capabilities) {
39908
+ return { resolve: () => capabilities };
39909
+ }
39910
+ function makeManualProviderMaintenanceCapabilities$1(provider) {
39911
+ return makeManualOnlyProviderMaintenanceCapabilities({
39912
+ provider,
39913
+ packageName: null
39914
+ });
39915
+ }
39916
+ const resolveProviderMaintenanceCapabilitiesEffect = fn("resolveProviderMaintenanceCapabilitiesEffect")(function* (resolver, options) {
39917
+ const binaryPath = nonEmptyString(options?.binaryPath);
39918
+ if (!binaryPath) return resolver.resolve(options);
39919
+ const resolvedCommandPath = resolveCommandPath(binaryPath, {
39920
+ ...options?.platform ? { platform: options.platform } : {},
39921
+ ...options?.env ? { env: options.env } : {}
39922
+ }) ?? (hasPathSeparator(binaryPath) ? binaryPath : null);
39923
+ if (!resolvedCommandPath) return resolver.resolve(options);
39924
+ const realCommandPath = yield* (yield* FileSystem).realPath(resolvedCommandPath).pipe(catch_(() => succeed(resolvedCommandPath)));
39925
+ return resolver.resolve({
39926
+ ...options,
39927
+ realCommandPath
39928
+ });
39929
+ });
39930
+ function deriveVersionAdvisory(input) {
39931
+ if (!input.currentVersion) return {
39932
+ status: "unknown",
39933
+ message: null
39934
+ };
39935
+ if (!input.latestVersion) return {
39936
+ status: "unknown",
39937
+ message: null
39938
+ };
39939
+ if (compareSemverVersions(input.currentVersion, input.latestVersion) < 0) return {
39940
+ status: "behind_latest",
39941
+ message: PROVIDER_UPDATE_ACTION_TOAST_MESSAGE
39942
+ };
39943
+ return {
39944
+ status: "current",
39945
+ message: null
39946
+ };
39947
+ }
39948
+ function createProviderVersionAdvisory(input) {
39949
+ const capabilities = input.maintenanceCapabilities ?? makeManualProviderMaintenanceCapabilities$1(input.driver);
39950
+ const latestVersion = input.latestVersion ?? null;
39951
+ const advisory = deriveVersionAdvisory({
39952
+ currentVersion: input.currentVersion,
39953
+ latestVersion
39954
+ });
39955
+ return {
39956
+ status: advisory.status,
39957
+ currentVersion: input.currentVersion,
39958
+ latestVersion,
39959
+ updateCommand: capabilities.update?.command ?? null,
39960
+ canUpdate: capabilities.update !== null,
39961
+ checkedAt: input.checkedAt ?? null,
39962
+ message: advisory.message
39963
+ };
39964
+ }
39965
+ const fetchNpmLatestVersion = fn("fetchNpmLatestVersion")(function* (packageName) {
39966
+ const client = yield* HttpClient;
39967
+ const request = get$7(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`).pipe(setHeader("accept", "application/json"));
39968
+ const response = yield* client.execute(request).pipe(timeoutOption(LATEST_VERSION_TIMEOUT_MS), catch_(() => succeed(none$4())));
39969
+ if (isNone(response)) return null;
39970
+ const httpResponse = response.value;
39971
+ if (httpResponse.status < 200 || httpResponse.status >= 300) return null;
39972
+ const payload = yield* httpResponse.json.pipe(flatMap(decodeUnknownEffect(NpmLatestVersionResponse)), catch_(() => succeed(null)));
39973
+ return payload ? nonEmptyString(payload.version) : null;
39974
+ });
39975
+ const resolveLatestProviderVersion = fn("resolveLatestProviderVersion")(function* (maintenanceCapabilities) {
39976
+ const packageName = maintenanceCapabilities.packageName;
39977
+ if (!packageName) return null;
39978
+ const cached = latestVersionCache.get(packageName);
39979
+ const now$6 = toEpochMillis(yield* now);
39980
+ if (cached && cached.expiresAt > now$6) return cached.version;
39981
+ const version = yield* fetchNpmLatestVersion(packageName);
39982
+ latestVersionCache.set(packageName, {
39983
+ expiresAt: now$6 + LATEST_VERSION_CACHE_TTL_MS,
39984
+ version
39985
+ });
39986
+ return version;
39987
+ });
39988
+ const enrichProviderSnapshotWithVersionAdvisory = fn("enrichProviderSnapshotWithVersionAdvisory")(function* (snapshot, maintenanceCapabilities) {
39989
+ const capabilities = maintenanceCapabilities ?? makeManualProviderMaintenanceCapabilities$1(snapshot.driver);
39990
+ if (!snapshot.enabled || !snapshot.installed || !snapshot.version) return {
39991
+ ...snapshot,
39992
+ versionAdvisory: createProviderVersionAdvisory({
39993
+ driver: snapshot.driver,
39994
+ currentVersion: snapshot.version,
39995
+ checkedAt: snapshot.checkedAt,
39996
+ maintenanceCapabilities: capabilities
39997
+ })
39998
+ };
39999
+ const latestVersion = yield* resolveLatestProviderVersion(capabilities);
40000
+ return {
40001
+ ...snapshot,
40002
+ versionAdvisory: createProviderVersionAdvisory({
40003
+ driver: snapshot.driver,
40004
+ currentVersion: snapshot.version,
40005
+ latestVersion,
40006
+ checkedAt: formatIso(yield* now),
40007
+ maintenanceCapabilities: capabilities
40008
+ })
40009
+ };
40010
+ });
40011
+
40012
+ //#endregion
40013
+ //#region src/stream/collectUint8StreamText.ts
40014
+ const collectUint8StreamText = (input) => {
40015
+ const decoder = new TextDecoder();
40016
+ const maxBytes = input.maxBytes ?? Number.POSITIVE_INFINITY;
40017
+ const truncatedMarker = input.truncatedMarker ?? "";
40018
+ return input.stream.pipe(runFold(() => ({
40019
+ text: "",
40020
+ bytes: 0,
40021
+ truncated: false
40022
+ }), (state, chunk) => {
40023
+ if (state.truncated) return state;
40024
+ const remainingBytes = maxBytes - state.bytes;
40025
+ if (remainingBytes <= 0) return {
40026
+ ...state,
40027
+ text: `${state.text}${truncatedMarker}`,
40028
+ truncated: true
40029
+ };
40030
+ const nextChunk = chunk.byteLength > remainingBytes ? chunk.slice(0, remainingBytes) : chunk;
40031
+ const text = `${state.text}${decoder.decode(nextChunk, { stream: true })}`;
40032
+ const bytes = state.bytes + nextChunk.byteLength;
40033
+ const truncated = chunk.byteLength > remainingBytes;
40034
+ return {
40035
+ text: truncated ? `${text}${truncatedMarker}` : text,
40036
+ bytes,
40037
+ truncated
40038
+ };
40039
+ }), map$3((state) => ({
40040
+ text: state.truncated ? state.text : `${state.text}${decoder.decode()}`,
40041
+ bytes: state.bytes,
40042
+ truncated: state.truncated
40043
+ })));
40044
+ };
40045
+
39874
40046
  //#endregion
39875
40047
  //#region src/provider/providerMaintenanceRunner.ts
39876
40048
  const isServerProviderUpdateError = is(ServerProviderUpdateError);
@@ -40257,9 +40429,8 @@ const makeCachedFsScanner = (config) => gen(function* () {
40257
40429
 
40258
40430
  //#endregion
40259
40431
  //#region src/ru-fork/common/cliRoots.ts
40260
- const cliUserRoot = (baseDir, subdir) => gen(function* () {
40261
- const path = yield* Path;
40262
- return path.join(path.dirname(baseDir), CLI_FOLDER, subdir);
40432
+ const cliUserRoot = (configDir, subdir) => gen(function* () {
40433
+ return (yield* Path).join(configDir, subdir);
40263
40434
  });
40264
40435
  const cliProjectRoot = (cwd, subdir) => gen(function* () {
40265
40436
  return (yield* Path).join(cwd, CLI_FOLDER, subdir);
@@ -40390,7 +40561,7 @@ const makeScanner$1 = gen(function* () {
40390
40561
  itemSchema: ServerProviderSkill,
40391
40562
  scanUser: (now) => gen(function* () {
40392
40563
  return {
40393
- items: yield* scanCliSkillsDir(yield* cliUserRoot(config.baseDir, SKILLS_SUBDIR), SCOPE_USER),
40564
+ items: yield* scanCliSkillsDir(yield* cliUserRoot(config.cliConfigDir, SKILLS_SUBDIR), SCOPE_USER),
40394
40565
  scannedAt: now
40395
40566
  };
40396
40567
  }),
@@ -40508,7 +40679,7 @@ const makeScanner = gen(function* () {
40508
40679
  itemSchema: ServerProviderSubagent,
40509
40680
  scanUser: (now) => gen(function* () {
40510
40681
  return {
40511
- items: yield* scanCliAgentsDir(yield* cliUserRoot(config.baseDir, AGENTS_SUBDIR), SCOPE_USER),
40682
+ items: yield* scanCliAgentsDir(yield* cliUserRoot(config.cliConfigDir, AGENTS_SUBDIR), SCOPE_USER),
40512
40683
  scannedAt: now
40513
40684
  };
40514
40685
  }),
@@ -46663,8 +46834,15 @@ const makeProviderService = fn("makeProviderService")(function* (options) {
46663
46834
  const routed = yield* resolveRoutableSession({
46664
46835
  threadId: input.threadId,
46665
46836
  operation: "ProviderService.interruptTurn",
46666
- allowRecovery: true
46837
+ allowRecovery: false
46667
46838
  });
46839
+ if (!routed.isActive) {
46840
+ yield* logDebug("[provider.interruptTurn] no live session — skipping (no-op)", {
46841
+ threadId: input.threadId,
46842
+ operation: "ProviderService.interruptTurn"
46843
+ });
46844
+ return;
46845
+ }
46668
46846
  metricProvider = routed.adapter.provider;
46669
46847
  yield* annotateCurrentSpan({
46670
46848
  "provider.operation": "interrupt-turn",
@@ -47568,7 +47746,7 @@ function extractCliResultText(rawStdout) {
47568
47746
  const assistantMsg = messages.find((m) => m.type === "assistant");
47569
47747
  return ((assistantMsg && assistantMsg.message?.content?.find((part) => part.type === "text")?.text) ?? "").trim();
47570
47748
  }
47571
- const makeCliTextGeneration = fn("makeCliTextGeneration")(function* (cliSettings, environment = process.env) {
47749
+ const makeCliTextGeneration = fn("makeCliTextGeneration")(function* (cliJs, cliSettings, environment = process.env) {
47572
47750
  const commandSpawner = yield* ChildProcessSpawner;
47573
47751
  const cliEnvironment = {
47574
47752
  ...environment,
@@ -47576,12 +47754,12 @@ const makeCliTextGeneration = fn("makeCliTextGeneration")(function* (cliSettings
47576
47754
  };
47577
47755
  const readStreamAsString = (operation, stream) => stream.pipe(decodeText(), runFold(() => "", (acc, chunk) => acc + chunk), mapError((cause) => normalizeCliError(CLI_NAME$1, operation, cause, "Failed to collect process output")));
47578
47756
  const runCliCommand = fn("runCliCommand")(function* (input) {
47579
- const resolved = resolveSpawn(CLI_BINARY_NAME, [
47757
+ const resolved = buildCliSpawn(cliJs, [
47580
47758
  "-p",
47581
47759
  input.prompt,
47582
47760
  "--output-format",
47583
47761
  "json"
47584
- ], { shell: process.platform === "win32" });
47762
+ ]);
47585
47763
  const command = make$46(resolved.command, [...resolved.args], {
47586
47764
  env: cliEnvironment,
47587
47765
  cwd: input.cwd,
@@ -52008,14 +52186,13 @@ const makeAcpSessionRuntime = (options) => gen(function* () {
52008
52186
  status: "failed",
52009
52187
  cause
52010
52188
  })))));
52011
- const resolved = resolveSpawn(options.spawn.command, options.spawn.args, { shell: process.platform === "win32" });
52012
- const child = yield* spawner.spawn(make$46(resolved.command, [...resolved.args], {
52189
+ const child = yield* spawner.spawn(make$46(options.spawn.command, [...options.spawn.args], {
52013
52190
  ...options.spawn.cwd ? { cwd: options.spawn.cwd } : {},
52014
52191
  ...options.spawn.env ? { env: {
52015
52192
  ...process.env,
52016
52193
  ...options.spawn.env
52017
52194
  } } : {},
52018
- shell: resolved.shell
52195
+ shell: false
52019
52196
  })).pipe(provideService(Scope, runtimeScope), mapError((cause) => new AcpSpawnError({
52020
52197
  command: options.spawn.command,
52021
52198
  cause
@@ -52464,14 +52641,15 @@ function parseLaunchArgs(launchArgs) {
52464
52641
  if (!trimmed || trimmed.length === 0) return [];
52465
52642
  return trimmed.split(/\s+/);
52466
52643
  }
52467
- function buildCliAcpSpawnInput(cliSettings, cwd, environment) {
52644
+ function buildCliAcpSpawnInput(cliJs, cliSettings, cwd, environment) {
52468
52645
  const env = { ...environment };
52469
52646
  const homePath = cliSettings?.homePath?.trim();
52470
52647
  if (homePath) env.CLI_HOME = homePath;
52471
52648
  if (ACP_SERVER_NO_SSL) env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
52649
+ const spawn = buildCliSpawn(cliJs, [...parseLaunchArgs(cliSettings?.launchArgs), "--acp"]);
52472
52650
  return {
52473
- command: CLI_BINARY_NAME,
52474
- args: [...parseLaunchArgs(cliSettings?.launchArgs), "--acp"],
52651
+ command: spawn.command,
52652
+ args: [...spawn.args],
52475
52653
  cwd,
52476
52654
  ...Object.keys(env).length > 0 ? { env } : {}
52477
52655
  };
@@ -52479,7 +52657,7 @@ function buildCliAcpSpawnInput(cliSettings, cwd, environment) {
52479
52657
  const makeCliAcpRuntime = (input) => gen(function* () {
52480
52658
  const acpContext = yield* build(AcpSessionRuntime.layer({
52481
52659
  ...input,
52482
- spawn: buildCliAcpSpawnInput(input.cliSettings, input.cwd, input.environment),
52660
+ spawn: buildCliAcpSpawnInput(input.cliJs, input.cliSettings, input.cwd, input.environment),
52483
52661
  authMethodId: CLI_AUTH_METHOD_ID
52484
52662
  }).pipe(provide$2(succeed$3(ChildProcessSpawner, input.childProcessSpawner))));
52485
52663
  return yield* service(AcpSessionRuntime).pipe(provide(acpContext));
@@ -53194,6 +53372,7 @@ function makeCliAdapter(cliSettings, options) {
53194
53372
  cliSettings,
53195
53373
  ...options?.environment ? { environment: options.environment } : {},
53196
53374
  childProcessSpawner,
53375
+ cliJs: serverConfig.cliJs,
53197
53376
  cwd,
53198
53377
  clientInfo: {
53199
53378
  name: "t3-code",
@@ -53870,6 +54049,40 @@ function makeCliAdapter(cliSettings, options) {
53870
54049
  });
53871
54050
  }
53872
54051
 
54052
+ //#endregion
54053
+ //#region src/provider/providerSnapshot.ts
54054
+ function isCommandMissingCause(error) {
54055
+ const lower = error.message.toLowerCase();
54056
+ return lower.includes("enoent") || lower.includes("notfound");
54057
+ }
54058
+ function parseGenericCliVersion(output) {
54059
+ return output.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
54060
+ }
54061
+ function buildServerProvider(input) {
54062
+ const versionAdvisory = input.driver ? createProviderVersionAdvisory({
54063
+ driver: input.driver,
54064
+ currentVersion: input.probe.version,
54065
+ checkedAt: input.checkedAt
54066
+ }) : void 0;
54067
+ return {
54068
+ displayName: input.presentation.displayName,
54069
+ ...input.presentation.badgeLabel ? { badgeLabel: input.presentation.badgeLabel } : {},
54070
+ ...typeof input.presentation.showInteractionModeToggle === "boolean" ? { showInteractionModeToggle: input.presentation.showInteractionModeToggle } : {},
54071
+ enabled: input.enabled,
54072
+ installed: input.probe.installed,
54073
+ version: input.probe.version,
54074
+ status: input.enabled ? input.probe.status : "disabled",
54075
+ auth: input.probe.auth,
54076
+ checkedAt: input.checkedAt,
54077
+ ...input.probe.message ? { message: input.probe.message } : {},
54078
+ models: input.models,
54079
+ slashCommands: [...input.slashCommands ?? []],
54080
+ skills: [...input.skills ?? []],
54081
+ ...versionAdvisory ? { versionAdvisory } : {}
54082
+ };
54083
+ }
54084
+ const collectStreamAsString = (stream) => collectUint8StreamText({ stream }).pipe(map$3((collected) => collected.text));
54085
+
53873
54086
  //#endregion
53874
54087
  //#region src/provider/Layers/CliProvider.ts
53875
54088
  const VERSION_TIMEOUT_MS = 8e3;
@@ -53928,9 +54141,9 @@ function buildInitialCliProviderSnapshot(cliSettings) {
53928
54141
  });
53929
54142
  });
53930
54143
  }
53931
- const runCliVersionCommand = (cliSettings, environment = process.env) => gen(function* () {
54144
+ const runCliVersionCommand = (cliJs, environment = process.env) => gen(function* () {
53932
54145
  const spawner = yield* ChildProcessSpawner;
53933
- const resolved = resolveSpawn(CLI_BINARY_NAME, ["--version"], { shell: process.platform === "win32" });
54146
+ const resolved = buildCliSpawn(cliJs, ["--version"]);
53934
54147
  const command = make$46(resolved.command, [...resolved.args], {
53935
54148
  env: environment,
53936
54149
  shell: resolved.shell
@@ -53947,7 +54160,7 @@ const runCliVersionCommand = (cliSettings, environment = process.env) => gen(fun
53947
54160
  code: exitCode
53948
54161
  };
53949
54162
  }).pipe(scoped);
53950
- const checkCliProviderStatus = fn("checkCliProviderStatus")(function* (cliSettings, environment = process.env) {
54163
+ const checkCliProviderStatus = fn("checkCliProviderStatus")(function* (cliJs, cliSettings, environment = process.env) {
53951
54164
  const checkedAt = formatIso(yield* now);
53952
54165
  if (!cliSettings.enabled) return buildServerProvider({
53953
54166
  presentation: CLI_PRESENTATION,
@@ -53962,7 +54175,7 @@ const checkCliProviderStatus = fn("checkCliProviderStatus")(function* (cliSettin
53962
54175
  message: `${CLI_NAME} отключён в настройках ${APP_NAME}.`
53963
54176
  }
53964
54177
  });
53965
- const versionProbe = yield* runCliVersionCommand(cliSettings, environment).pipe(timeoutOption(VERSION_TIMEOUT_MS), result);
54178
+ const versionProbe = yield* runCliVersionCommand(cliJs, environment).pipe(timeoutOption(VERSION_TIMEOUT_MS), result);
53966
54179
  if (isFailure$1(versionProbe)) {
53967
54180
  const error = versionProbe.failure;
53968
54181
  return buildServerProvider({
@@ -54138,6 +54351,7 @@ const CliDriver = {
54138
54351
  defaultConfig: () => decodeCliSettings({}),
54139
54352
  create: ({ instanceId, displayName, accentColor, environment, enabled, config }) => gen(function* () {
54140
54353
  const spawner = yield* ChildProcessSpawner;
54354
+ const serverConfig = yield* ServerConfig;
54141
54355
  const eventLoggers = yield* ProviderEventLoggers;
54142
54356
  const processEnv = mergeProviderInstanceEnvironment(environment);
54143
54357
  const continuationIdentity = defaultProviderContinuationIdentity({
@@ -54163,8 +54377,8 @@ const CliDriver = {
54163
54377
  ...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {},
54164
54378
  instanceId
54165
54379
  });
54166
- const textGeneration = yield* makeCliTextGeneration(effectiveConfig, processEnv);
54167
- const checkProvider = checkCliProviderStatus(effectiveConfig, processEnv).pipe(map$3(stampIdentity), provideService(ChildProcessSpawner, spawner));
54380
+ const textGeneration = yield* makeCliTextGeneration(serverConfig.cliJs, effectiveConfig, processEnv);
54381
+ const checkProvider = checkCliProviderStatus(serverConfig.cliJs, effectiveConfig, processEnv).pipe(map$3(stampIdentity), provideService(ChildProcessSpawner, spawner));
54168
54382
  return {
54169
54383
  instanceId,
54170
54384
  driverKind: DRIVER_KIND,
@@ -57732,6 +57946,13 @@ const make$3 = gen(function* () {
57732
57946
  const turnId = toTurnId$1(event.turnId);
57733
57947
  if (turnId) {
57734
57948
  const assistantMessageIds = yield* getAssistantMessageIdsForTurn(thread.id, turnId);
57949
+ yield* logDebug("[ingestion.turn-completed] finalize assistant messages", {
57950
+ threadId: thread.id,
57951
+ turnId,
57952
+ state: event.type === "turn.completed" ? event.payload.state : void 0,
57953
+ assistantMessageIdCount: assistantMessageIds.size,
57954
+ assistantMessageIds: Array.from(assistantMessageIds)
57955
+ });
57735
57956
  yield* forEach(assistantMessageIds, (assistantMessageId) => finalizeAssistantMessage({
57736
57957
  event,
57737
57958
  threadId: thread.id,
@@ -60850,8 +61071,9 @@ const runServerCommand = (flags, options) => gen(function* () {
60850
61071
  injectExtraPaths: getOrUndefined(flags.injectExtraPaths),
60851
61072
  windowsUseBashFor: getOrUndefined(flags.windowsUseBashFor)
60852
61073
  });
60853
- if (!getOrElse(flags.noPreflightCheck, () => false)) yield* runPreflight;
60854
- const config = yield* resolveServerConfig(flags, yield* LogLevel, options);
61074
+ const cli = yield* resolveStartupCli;
61075
+ if (!getOrElse(flags.noPreflightCheck, () => false)) yield* runStartupChecks(cli.cliJs);
61076
+ const config = yield* resolveServerConfig(flags, yield* LogLevel, cli, options);
60855
61077
  if (config.mode === DESKTOP_RUNTIME_MODE) yield* assertLocalPortAvailable(config.port);
60856
61078
  return yield* runServer.pipe(provideService(ServerConfig, config), provide(succeed$3(MinimumLogLevel, config.logLevel)));
60857
61079
  });
@@ -61028,7 +61250,7 @@ const fetchPairingStartupUrl = (origin) => tryPromise({
61028
61250
  })
61029
61251
  }).pipe(catch_(() => succeed(null)));
61030
61252
  const resolveDerivedPaths = (input) => gen(function* () {
61031
- return yield* deriveServerPaths(yield* resolveBaseDir(input.baseDirOverride !== void 0 ? yield* expandHomePath$4(input.baseDirOverride.trim()) : void 0), input.devUrlOverride);
61253
+ return yield* deriveServerPaths(yield* resolveBaseDir((input.baseDirOverride !== void 0 ? yield* expandHomePath$4(input.baseDirOverride.trim()) : void 0) ?? input.ourRoot), input.devUrlOverride);
61032
61254
  });
61033
61255
  const announceReady = (input) => gen(function* () {
61034
61256
  yield* printDaemonReadyBanner({
@@ -61043,8 +61265,12 @@ const runDaemonLauncher = (input) => gen(function* () {
61043
61265
  injectExtraPaths: input.injectExtraPaths,
61044
61266
  windowsUseBashFor: input.windowsUseBashFor
61045
61267
  });
61046
- if (!input.noPreflightCheck) yield* runPreflight;
61047
- const { serverRuntimeStatePath, logsDir } = yield* resolveDerivedPaths(input);
61268
+ const cli = yield* resolveStartupCli;
61269
+ if (!input.noPreflightCheck) yield* runStartupChecks(cli.cliJs);
61270
+ const { serverRuntimeStatePath, logsDir } = yield* resolveDerivedPaths({
61271
+ ...input,
61272
+ ourRoot: cli.ourRoot
61273
+ });
61048
61274
  const launcherLogPath = path$1.join(logsDir, "ru-fork.log");
61049
61275
  const existingState = yield* readPersistedServerRuntimeState(serverRuntimeStatePath);
61050
61276
  if (existingState._tag === "Some") {
@@ -61082,7 +61308,11 @@ const runDaemonLauncher = (input) => gen(function* () {
61082
61308
  });
61083
61309
  });
61084
61310
  const runStopCommand = (input) => gen(function* () {
61085
- const state = yield* readPersistedServerRuntimeState((yield* resolveDerivedPaths(input)).serverRuntimeStatePath);
61311
+ const cli = yield* resolveStartupCli;
61312
+ const state = yield* readPersistedServerRuntimeState((yield* resolveDerivedPaths({
61313
+ ...input,
61314
+ ourRoot: cli.ourRoot
61315
+ })).serverRuntimeStatePath);
61086
61316
  if (state._tag !== "Some") {
61087
61317
  yield* log("");
61088
61318
  yield* log(headline(ARROW_DIM, "ru-fork не запущен"));