scream-code 0.16.8 → 0.16.9

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.
@@ -4,9 +4,9 @@ import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
6
  import { i as __require, o as __toESM, r as __exportAll, t as __commonJSMin } from "./chunk-D90kvbyJ.mjs";
7
- import { A as classifyEmbeddingFailure, B as rankMemos, C as applyConsolidation, D as parseMemoryMemos, E as buildExitExtractionPrompt, F as createMemoryMemo, G as normalize$1, H as dirname$2, I as toSummary, J as resolve$2, K as parse$7, L as generateTags, M as createFastEmbedEngine, N as hasEmbeddingModelCache$1, O as stripMemoryTags, P as probeLocalEmbeddingSupport, R as normalizeTags, S as DreamTracker, T as EXIT_EXTRACTION_SYSTEM_PROMPT, U as isAbsolute$1, V as basename$1, W as join$1, a as isSupportedFile, b as sharedKnowledgeStoreReady, i as ingestFile, j as clearEmbeddingModelCache, k as MemoryMemoStore, q as relative$1, r as ingestDirectory, t as multiSearch, w as buildConsolidationPlan, y as sharedKnowledgeStore, z as buildProjectTagCloud } from "./src-CXmZg1VT.mjs";
7
+ import { A as classifyEmbeddingFailure, B as rankMemos, C as applyConsolidation, D as parseMemoryMemos, E as buildExitExtractionPrompt, F as createMemoryMemo, G as normalize, H as dirname$2, I as toSummary, J as resolve$2, K as parse$7, L as generateTags, M as createFastEmbedEngine, N as hasEmbeddingModelCache$1, O as stripMemoryTags, P as probeLocalEmbeddingSupport, R as normalizeTags, S as DreamTracker, T as EXIT_EXTRACTION_SYSTEM_PROMPT, U as isAbsolute$1, V as basename$1, W as join$1, a as isSupportedFile, b as sharedKnowledgeStoreReady, i as ingestFile, j as clearEmbeddingModelCache, k as MemoryMemoStore, q as relative$1, r as ingestDirectory, t as multiSearch, w as buildConsolidationPlan, y as sharedKnowledgeStore, z as buildProjectTagCloud } from "./src-BhZujafw.mjs";
8
8
  import { t as require_base64_js } from "./base64-js-DzVmk6Nb.mjs";
9
- import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-6v2667rm.mjs";
9
+ import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-XT3cOEjD.mjs";
10
10
  import { createRequire } from "node:module";
11
11
  import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
12
12
  import * as fs$1 from "node:fs/promises";
@@ -24,12 +24,12 @@ import * as NodeWs from "ws";
24
24
  import * as fs$10 from "node:fs";
25
25
  import Vt, { appendFileSync, chmodSync, closeSync, constants, createReadStream, createWriteStream as createWriteStream$1, existsSync, fchmodSync, fsyncSync, mkdirSync, openSync, promises, readFileSync, readSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
26
26
  import * as path$8 from "node:path";
27
- import path, { basename, dirname as dirname$1, extname, isAbsolute, join, normalize, posix, relative, resolve, sep, win32 } from "node:path";
27
+ import path, { basename, dirname as dirname$1, extname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
28
+ import * as nodeOs from "node:os";
29
+ import { homedir, release, tmpdir } from "node:os";
28
30
  import { z } from "zod";
29
31
  import { exec, execFile, execSync, spawn, spawnSync } from "node:child_process";
30
32
  import { promisify } from "node:util";
31
- import * as nodeOs from "node:os";
32
- import { homedir, release, tmpdir } from "node:os";
33
33
  import * as vs from "zlib";
34
34
  import Qr from "zlib";
35
35
  import { diffLines, diffWords } from "diff";
@@ -38,6 +38,7 @@ import { StringDecoder } from "node:string_decoder";
38
38
  import Pi from "assert";
39
39
  import ro from "node:assert";
40
40
  import { fileURLToPath, pathToFileURL } from "node:url";
41
+ import { AsyncLocalStorage } from "node:async_hooks";
41
42
  import * as posixPath from "node:path/posix";
42
43
  import * as win32Path from "node:path/win32";
43
44
  import { createServer } from "node:http";
@@ -48,7 +49,6 @@ import * as z4mini from "zod/v4-mini";
48
49
  import process$1 from "node:process";
49
50
  import { lookup } from "node:dns/promises";
50
51
  import { isIP } from "node:net";
51
- import { AsyncLocalStorage } from "node:async_hooks";
52
52
  import chalk from "chalk";
53
53
  import { Box, Container, Image, Input, Key, Markdown, Marked, Spacer, Text, decodeKittyPrintable, fuzzyFilter, getCapabilities, getImageDimensions, matchesKey, sliceByColumn, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@liutod-scream/pi-tui";
54
54
  import { highlight, supportsLanguage } from "cli-highlight";
@@ -47417,6 +47417,20 @@ function resolveGlobalLogPath(homeDir) {
47417
47417
  return join$1(homeDir, "logs", "scream-code.log");
47418
47418
  }
47419
47419
  //#endregion
47420
+ //#region ../../packages/agent-core/src/config/path.ts
47421
+ function resolveScreamHome(homeDir) {
47422
+ return homeDir ?? process.env["SCREAM_CODE_HOME"] ?? join$1(homedir(), ".scream-code");
47423
+ }
47424
+ function resolveConfigPath(input) {
47425
+ return input.configPath ?? join$1(resolveScreamHome(input.homeDir), "config.toml");
47426
+ }
47427
+ function ensureScreamHome(homeDir) {
47428
+ mkdirSync(homeDir, {
47429
+ recursive: true,
47430
+ mode: 448
47431
+ });
47432
+ }
47433
+ //#endregion
47420
47434
  //#region ../../node_modules/.pnpm/@antfu+utils@9.3.0/node_modules/@antfu/utils/dist/index.mjs
47421
47435
  function uniq(array) {
47422
47436
  return Array.from(new Set(array));
@@ -50853,15 +50867,15 @@ function canonicalizePath(path, cwd, pathClass = DEFAULT_PATH_CLASS) {
50853
50867
  const normalizedPath = normalizeUserPath(path, pathClass);
50854
50868
  if (pathClass === "win32" && isWin32DriveRelative(normalizedPath)) throw new PathSecurityError("PATH_INVALID", path, normalizedPath, `"${path}" is a drive-relative Windows path. Use an absolute path like C:\\path or a path relative to the working directory.`);
50855
50869
  if (!isAbsolute$1(normalizedPath) && !isAbsolute$1(cwd)) throw new PathSecurityError("PATH_INVALID", path, normalizedPath, `Cannot resolve "${path}" against non-absolute cwd "${cwd}".`);
50856
- return normalize$1(isAbsolute$1(normalizedPath) ? normalizedPath : resolve$2(cwd, normalizedPath));
50870
+ return normalize(isAbsolute$1(normalizedPath) ? normalizedPath : resolve$2(cwd, normalizedPath));
50857
50871
  }
50858
50872
  /**
50859
50873
  * True iff `candidate` is `base` itself or a descendant of it, compared
50860
50874
  * on path-component boundaries. Both arguments must already be canonical.
50861
50875
  */
50862
50876
  function isWithinDirectory$1(candidate, base, pathClass = DEFAULT_PATH_CLASS) {
50863
- const nc = normalize$1(candidate);
50864
- const nb = normalize$1(base);
50877
+ const nc = normalize(candidate);
50878
+ const nb = normalize(base);
50865
50879
  const comparableCandidate = pathClass === "win32" ? nc.toLowerCase() : nc;
50866
50880
  const comparableBase = pathClass === "win32" ? nb.toLowerCase() : nb;
50867
50881
  if (comparableCandidate === comparableBase) return true;
@@ -53888,7 +53902,7 @@ var KnowledgeLookupTool = class {
53888
53902
  const llm = { generate: async (systemPrompt, userPrompt) => {
53889
53903
  return this.agent.generateText(systemPrompt, userPrompt);
53890
53904
  } };
53891
- const { multiSearchWithTrace } = await import("./src-DIAKpkXO.mjs");
53905
+ const { multiSearchWithTrace } = await import("./src-CUURS-i6.mjs");
53892
53906
  const { results, trace } = await multiSearchWithTrace(store, llm, query, {
53893
53907
  topK,
53894
53908
  skipLlm: args.deep !== true
@@ -59089,7 +59103,7 @@ const MANAGED_SKILL_ROOT_NAMES = new Set([".scream-code", ".agents"]);
59089
59103
  * `<...>/.agents/skills`.
59090
59104
  */
59091
59105
  function resolveSkillInstallUnit(skillPath) {
59092
- let current = normalize$1(skillPath).replaceAll("\\", "/");
59106
+ let current = normalize(skillPath).replaceAll("\\", "/");
59093
59107
  while (true) {
59094
59108
  const parent = dirname$2(current).replaceAll("\\", "/");
59095
59109
  if (parent === current || parent === ".") throw new Error(`Skill path "${skillPath}" is not under a managed skill root`);
@@ -64334,6 +64348,89 @@ function escapeXml(input) {
64334
64348
  return input.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
64335
64349
  }
64336
64350
  //#endregion
64351
+ //#region ../../packages/agent-core/src/utils/path-safety.ts
64352
+ /**
64353
+ * Cross-platform path classification for call sites that must decide whether
64354
+ * a path is absolute and whether it sits inside a directory.
64355
+ *
64356
+ * The paths reaching these helpers need not match the host OS: a
64357
+ * Windows-shaped path can arrive at a macOS/Linux process (a config value, a
64358
+ * remote workspace, a session recorded on another machine). `node:path`
64359
+ * derives absoluteness from `process.platform`, so `C:\x` and
64360
+ * `\\server\share` silently read as ordinary relative names there and get
64361
+ * appended under a directory the caller never intended. These helpers instead
64362
+ * classify every supported flavour explicitly — POSIX (`/x`, `\x`), Windows
64363
+ * drive (`C:\x`, `C:/x`, drive-relative `C:x`) and UNC
64364
+ * (`\\server\share`) — on top of `pathe`, the path library the rest of this
64365
+ * package uses.
64366
+ */
64367
+ /** `C:`, `C:\x`, `C:/x`, and the drive-relative `C:x`. */
64368
+ const WINDOWS_DRIVE = /^[A-Za-z]:/;
64369
+ /** `\\server\share` and `//server/share`. */
64370
+ const WINDOWS_UNC = /^[/\\]{2}[^/\\]/;
64371
+ /** True when `path` is written in Windows syntax (drive-qualified or UNC). */
64372
+ function usesWindowsSyntax(path) {
64373
+ return WINDOWS_DRIVE.test(path) || WINDOWS_UNC.test(path);
64374
+ }
64375
+ /**
64376
+ * Normalize a candidate that declares its own root.
64377
+ *
64378
+ * `//x` is UNC syntax only next to a Windows `base`: a POSIX caller passing
64379
+ * `//work/file.ts` means the POSIX path `/work/file.ts`, and comparing the
64380
+ * doubled slash as written would place a file inside the working directory
64381
+ * outside it. The Windows `\\server\share` spelling is left alone — it is UNC
64382
+ * in every context, and folding it into the POSIX namespace would widen the
64383
+ * check instead of narrowing it.
64384
+ */
64385
+ function normalizeRootedCandidate(candidate, baseIsWindows) {
64386
+ if (!baseIsWindows && candidate.startsWith("//")) return normalize(candidate.replace(/^\/+/, "/"));
64387
+ return normalize(candidate);
64388
+ }
64389
+ /**
64390
+ * True when `path` names its own root instead of one relative to a base:
64391
+ * POSIX-absolute (`/x`, `\x`), UNC-rooted, or drive-qualified. `isAbsolute`
64392
+ * covers the first three; the drive test adds `C:x`, which is relative to
64393
+ * *the drive's* current directory and therefore equally unappendable under a
64394
+ * base.
64395
+ */
64396
+ function declaresOwnRoot(path) {
64397
+ return isAbsolute$1(path) || WINDOWS_DRIVE.test(path);
64398
+ }
64399
+ /**
64400
+ * True when `candidate` is `base` itself or a descendant of it.
64401
+ *
64402
+ * A relative `candidate` is resolved against `base`; one that declares its
64403
+ * own root is compared as written, so `C:\outside\f.txt` is outside a POSIX
64404
+ * `base` on every host. Comparison is made on path components (`/work-evil`
64405
+ * is not inside `/work`) and case-insensitively when both sides use Windows
64406
+ * syntax, because Windows filenames are case-insensitive.
64407
+ */
64408
+ function isPathInside(base, candidate) {
64409
+ const basePath = normalize(base);
64410
+ const baseIsWindows = usesWindowsSyntax(basePath);
64411
+ const candidatePath = declaresOwnRoot(candidate) ? normalizeRootedCandidate(candidate, baseIsWindows) : join$1(basePath, candidate);
64412
+ const foldCase = baseIsWindows && usesWindowsSyntax(candidatePath);
64413
+ const lhs = foldCase ? basePath.toLowerCase() : basePath;
64414
+ const rhs = foldCase ? candidatePath.toLowerCase() : candidatePath;
64415
+ if (lhs === rhs) return true;
64416
+ return rhs.startsWith(lhs.endsWith("/") ? lhs : `${lhs}/`);
64417
+ }
64418
+ /**
64419
+ * True when `candidate` may be appended under a directory: it declares no
64420
+ * root of its own and does not climb out of the directory it is appended to.
64421
+ *
64422
+ * The check is lexical and runs on the normalized form, so `a/../b.txt` is
64423
+ * accepted while `../b.txt`, `a/../../x` and `sub/..` are rejected. Only
64424
+ * `..` path *components* are unsafe — `..foo.txt` is an ordinary file name.
64425
+ */
64426
+ function isSafeRelativePath(candidate) {
64427
+ if (candidate.trim().length === 0) return false;
64428
+ if (declaresOwnRoot(candidate)) return false;
64429
+ const normalized = normalize(candidate);
64430
+ if (normalized === "." || normalized === "..") return false;
64431
+ return !normalized.startsWith("../");
64432
+ }
64433
+ //#endregion
64337
64434
  //#region ../../packages/agent-core/src/tools/builtin/skill/skill-package-writer.ts
64338
64435
  const SKILL_TYPE_SCHEMA = z.enum([
64339
64436
  "workflow",
@@ -67610,12 +67707,6 @@ function isScreamNativeBinary() {
67610
67707
  //#endregion
67611
67708
  //#region ../../packages/agent-core/src/tools/builtin/skill/plugin-skill-package-writer.ts
67612
67709
  const MANIFEST_FILE = "scream.plugin.json";
67613
- function isSafeRelativePath(filePath) {
67614
- const normalized = normalize$1(filePath);
67615
- if (normalized.startsWith("..")) return false;
67616
- if (normalize$1("/" + normalized).startsWith("..")) return false;
67617
- return !normalized.startsWith("/");
67618
- }
67619
67710
  async function writePluginSkillPackage(options) {
67620
67711
  const { jian, screamHomeDir, manager, package: pkg } = options;
67621
67712
  const name = sanitizeSkillName(pkg.name);
@@ -67642,7 +67733,7 @@ async function writePluginSkillPackage(options) {
67642
67733
  const files = pkg.files ?? [];
67643
67734
  for (const file of files) {
67644
67735
  if (!isSafeRelativePath(file.path)) throw new ScreamError(ErrorCodes.REQUEST_INVALID, `Unsafe supporting file path "${file.path}". Paths must be relative and cannot escape the skill directory.`);
67645
- const filePath = join$1(targetDir, normalize$1(file.path));
67736
+ const filePath = join$1(targetDir, normalize(file.path));
67646
67737
  const fileDir = dirname$2(filePath);
67647
67738
  if (fileDir !== targetDir) await jian.mkdir(fileDir, {
67648
67739
  parents: true,
@@ -69043,8 +69134,8 @@ var GlobTool = class {
69043
69134
  * should be canonical absolute paths.
69044
69135
  */
69045
69136
  function relativizeIfUnder$1(candidate, base, pathClass) {
69046
- const normCandidate = normalize$1(candidate);
69047
- const normBase = normalize$1(base);
69137
+ const normCandidate = normalize(candidate);
69138
+ const normBase = normalize(base);
69048
69139
  const comparableCandidate = pathClass === "win32" ? normCandidate.toLowerCase() : normCandidate;
69049
69140
  const comparableBase = pathClass === "win32" ? normBase.toLowerCase() : normBase;
69050
69141
  if (comparableCandidate === comparableBase) return ".";
@@ -72086,7 +72177,7 @@ const RG_ARCHIVE_SHA256 = {
72086
72177
  */
72087
72178
  async function ensureRgPath(options = {}) {
72088
72179
  options.signal?.throwIfAborted();
72089
- const resolution = resolveRgPath(options.shareDir ?? getShareDir(), options.signal);
72180
+ const resolution = resolveRgPath(options.shareDir ?? resolveScreamHome(), options.signal);
72090
72181
  return options.signal === void 0 ? resolution : abortable(resolution, options.signal);
72091
72182
  }
72092
72183
  async function resolveRgPath(shareDir, signal) {
@@ -72107,7 +72198,7 @@ async function findExistingRg(shareDir) {
72107
72198
  source: "system-path"
72108
72199
  };
72109
72200
  const cachePath = join$1(shareDir, "bin", binName);
72110
- if (await isExecutableFile(cachePath)) return {
72201
+ if (await fileExists$1(cachePath)) return {
72111
72202
  path: cachePath,
72112
72203
  source: "share-bin-cached"
72113
72204
  };
@@ -72132,11 +72223,6 @@ async function downloadRgWithLock(shareDir) {
72132
72223
  function rgBinaryName() {
72133
72224
  return process.platform === "win32" ? "rg.exe" : "rg";
72134
72225
  }
72135
- function getShareDir() {
72136
- const override = process.env["SCREAM_CODE_HOME"];
72137
- if (override !== void 0 && override !== "") return override;
72138
- return join$1(homedir(), ".scream-code");
72139
- }
72140
72226
  async function whichRg() {
72141
72227
  const pathEnv = process.env["PATH"] ?? "";
72142
72228
  const sep = process.platform === "win32" ? ";" : ":";
@@ -72149,29 +72235,143 @@ async function whichRg() {
72149
72235
  } catch {}
72150
72236
  }
72151
72237
  }
72152
- async function isExecutableFile(p) {
72238
+ async function fileExists$1(p) {
72153
72239
  try {
72154
72240
  return (await stat(p)).isFile();
72155
72241
  } catch {
72156
72242
  return false;
72157
72243
  }
72158
72244
  }
72159
- /** @internal for tests — rust-style `<arch>-<vendor>-<os>` target triple. */
72160
- function detectTarget() {
72161
- const arch = process.arch === "x64" ? "x86_64" : process.arch === "arm64" ? "aarch64" : void 0;
72162
- if (arch === void 0) return void 0;
72163
- if (process.platform === "darwin") return `${arch}-apple-darwin`;
72164
- if (process.platform === "linux") return arch === "x86_64" ? "x86_64-unknown-linux-musl" : "aarch64-unknown-linux-gnu";
72165
- if (process.platform === "win32") return `${arch}-pc-windows-msvc`;
72245
+ /** Rust target-triple arch component, for the Node arches ripgrep ships binaries for. */
72246
+ const TARGET_ARCH = {
72247
+ x64: "x86_64",
72248
+ arm64: "aarch64"
72249
+ };
72250
+ /** Node arch → the arch component musl uses in its dynamic loader file name. */
72251
+ const LINUX_LOADER_ARCH = {
72252
+ x64: "x86_64",
72253
+ arm64: "aarch64"
72254
+ };
72255
+ /**
72256
+ * Decide whether the Linux host is musl- or glibc-based.
72257
+ *
72258
+ * The libc is probed, never inferred from the arch: a glibc-linked binary
72259
+ * cannot exec on a musl host, so guessing wrong hands the user a broken `rg`.
72260
+ * Three positive signals, each cheap, then a documented default:
72261
+ *
72262
+ * 1. `/etc/alpine-release` — Alpine states its own libc here, and the marker
72263
+ * wins over the probes below because Alpine also ships a glibc-compat
72264
+ * shim that would otherwise make the next signal claim "glibc".
72265
+ * 2. Node's diagnostic report `header.glibcVersionRuntime` — populated only
72266
+ * when the *running* binary bound to glibc, so it is strong evidence for
72267
+ * distros that carry no marker file.
72268
+ * 3. The musl dynamic loader `/lib/ld-musl-<arch>.so.1` — covers non-Alpine
72269
+ * musl distros and musl container images.
72270
+ * 4. Default `glibc`: the Linux baseline. A wrong guess here only changes the
72271
+ * outcome on aarch64 Linux, where it selects the glibc build.
72272
+ */
72273
+ async function detectLinuxLibc(probe = {}) {
72274
+ const isFile = probe.isFile ?? fileExists$1;
72275
+ if (await isFile("/etc/alpine-release")) return "musl";
72276
+ const glibcVersion = (probe.glibcVersionRuntime ?? nodeGlibcVersionRuntime)();
72277
+ if (glibcVersion !== void 0 && glibcVersion.length > 0) return "glibc";
72278
+ const loaderArch = LINUX_LOADER_ARCH[probe.arch ?? process.arch];
72279
+ if (loaderArch !== void 0 && await isFile(`/lib/ld-musl-${loaderArch}.so.1`)) return "musl";
72280
+ return "glibc";
72281
+ }
72282
+ function nodeGlibcVersionRuntime() {
72283
+ try {
72284
+ const version = process.report.getReport().header?.glibcVersionRuntime;
72285
+ return typeof version === "string" ? version : void 0;
72286
+ } catch {
72287
+ return;
72288
+ }
72166
72289
  }
72167
- async function downloadAndInstallRg(shareDir) {
72168
- const target = detectTarget();
72169
- if (target === void 0) throw new Error(`Unsupported platform/arch for ripgrep download: ${process.platform}/${process.arch}`);
72290
+ /**
72291
+ * The single decision tree behind {@link detectTarget} and
72292
+ * {@link resolveRgArchive}: one place that knows which host maps to which
72293
+ * upstream release, so the "which target" and "why is there none" answers
72294
+ * cannot drift apart.
72295
+ *
72296
+ * Linux is the only platform whose libc matters, and upstream publishes exactly
72297
+ * one Linux build per arch:
72298
+ * - x86_64: the static musl build only. Static musl runs unchanged on glibc
72299
+ * hosts, so both libcs resolve to the same archive and no probe is needed.
72300
+ * - aarch64: the glibc build only, so musl (e.g. Alpine) has no build at all.
72301
+ */
72302
+ async function decideTarget(probe) {
72303
+ const nodeArch = probe.arch ?? process.arch;
72304
+ const arch = TARGET_ARCH[nodeArch];
72305
+ if (arch === void 0) return {
72306
+ kind: "unavailable",
72307
+ reason: `unsupported architecture '${nodeArch}'`
72308
+ };
72309
+ const platform = probe.platform ?? process.platform;
72310
+ if (platform === "darwin") return {
72311
+ kind: "target",
72312
+ target: `${arch}-apple-darwin`
72313
+ };
72314
+ if (platform === "win32") return {
72315
+ kind: "target",
72316
+ target: `${arch}-pc-windows-msvc`
72317
+ };
72318
+ if (platform !== "linux") return {
72319
+ kind: "unavailable",
72320
+ reason: `unsupported platform '${platform}'`
72321
+ };
72322
+ if (arch === "x86_64") return {
72323
+ kind: "target",
72324
+ target: "x86_64-unknown-linux-musl"
72325
+ };
72326
+ if (await (probe.detectLinuxLibc !== void 0 ? probe.detectLinuxLibc() : detectLinuxLibc({ arch: nodeArch })) === "glibc") return {
72327
+ kind: "target",
72328
+ target: "aarch64-unknown-linux-gnu"
72329
+ };
72330
+ return {
72331
+ kind: "unavailable",
72332
+ reason: `no upstream ripgrep ${RG_VERSION} release exists for aarch64 Linux with musl (Alpine ARM64); upstream publishes glibc only for aarch64`
72333
+ };
72334
+ }
72335
+ /**
72336
+ * Build the download plan for one target triple, or throw when that archive is
72337
+ * not pinned in {@link RG_ARCHIVE_SHA256}. Kept separate from
72338
+ * {@link resolveRgArchive} so the pin gate is directly testable without
72339
+ * inventing a reachable-but-unpinned host.
72340
+ *
72341
+ * @internal for tests
72342
+ */
72343
+ function pinnedRgArchive(target) {
72170
72344
  const isWindows = target.includes("windows");
72171
- const archiveName = `ripgrep-${RG_VERSION}-${target}.${isWindows ? "zip" : "tar.gz"}`;
72172
- const expectedSha256 = RG_ARCHIVE_SHA256[archiveName];
72173
- if (expectedSha256 === void 0) throw new Error(`No pinned SHA-256 is configured for ripgrep archive ${archiveName}`);
72174
- const url = `${RG_BASE_URL}/${archiveName}`;
72345
+ const name = `ripgrep-${RG_VERSION}-${target}.${isWindows ? "zip" : "tar.gz"}`;
72346
+ const sha256 = RG_ARCHIVE_SHA256[name];
72347
+ if (sha256 === void 0) throw new Error(`No pinned SHA-256 is configured for ripgrep archive ${name}, so it will not be downloaded. Install ripgrep with your package manager and re-run.`);
72348
+ return {
72349
+ target,
72350
+ name,
72351
+ url: `${RG_BASE_URL}/${name}`,
72352
+ sha256,
72353
+ isWindows
72354
+ };
72355
+ }
72356
+ /**
72357
+ * Resolve the release archive to bootstrap — the only download gate.
72358
+ *
72359
+ * Throws (never falls back to a different target) when the host has no
72360
+ * supported build or when the resulting archive is not in
72361
+ * {@link RG_ARCHIVE_SHA256}: downloading bytes we cannot verify against a
72362
+ * pinned digest is worse than telling the user to install ripgrep themselves.
72363
+ * Callers turn the thrown message into user-facing text with
72364
+ * {@link rgUnavailableMessage}.
72365
+ *
72366
+ * @internal for tests
72367
+ */
72368
+ async function resolveRgArchive(probe = {}) {
72369
+ const decision = await decideTarget(probe);
72370
+ if (decision.kind === "unavailable") throw new Error(`No automatic ripgrep bootstrap is available on this host: ${decision.reason}. Install ripgrep with your package manager and re-run.`);
72371
+ return pinnedRgArchive(decision.target);
72372
+ }
72373
+ async function downloadAndInstallRg(shareDir) {
72374
+ const { name: archiveName, target, url, sha256: expectedSha256, isWindows } = await resolveRgArchive();
72175
72375
  const binDir = join$1(shareDir, "bin");
72176
72376
  await mkdir(binDir, { recursive: true });
72177
72377
  const destination = join$1(binDir, rgBinaryName());
@@ -72291,7 +72491,7 @@ async function extractRgFromZip(archivePath, destination) {
72291
72491
  function rgUnavailableMessage(cause) {
72292
72492
  return `ripgrep (rg) is not available and the automatic bootstrap failed.
72293
72493
 
72294
- Error: ${cause instanceof Error ? cause.message : typeof cause === "string" ? cause : "unknown error"}\n\nFix options:\n macOS: brew install ripgrep\n Ubuntu: sudo apt-get install ripgrep\n Other: https://github.com/BurntSushi/ripgrep#installation\n\nAlternatively, drop a static rg binary at ${join$1(getShareDir(), "bin", rgBinaryName())}`;
72494
+ Error: ${cause instanceof Error ? cause.message : typeof cause === "string" ? cause : "unknown error"}\n\nFix options:\n macOS: brew install ripgrep\n Ubuntu: sudo apt-get install ripgrep\n Other: https://github.com/BurntSushi/ripgrep#installation\n\nAlternatively, drop a static rg binary at ${join$1(resolveScreamHome(), "bin", rgBinaryName())}`;
72295
72495
  }
72296
72496
  //#endregion
72297
72497
  //#region ../../packages/agent-core/src/tools/support/rg-pattern-sanitize.ts
@@ -73034,8 +73234,8 @@ function formatDisplayLine(line, mode, workspaceDir, pathClass, contentIncludesL
73034
73234
  * canonical absolute paths in the active backend path class.
73035
73235
  */
73036
73236
  function relativizeIfUnder(candidate, base, pathClass) {
73037
- const normCandidate = normalize$1(candidate);
73038
- const normBase = normalize$1(base);
73237
+ const normCandidate = normalize(candidate);
73238
+ const normBase = normalize(base);
73039
73239
  const comparableCandidate = pathClass === "win32" ? normCandidate.toLowerCase() : normCandidate;
73040
73240
  const comparableBase = pathClass === "win32" ? normBase.toLowerCase() : normBase;
73041
73241
  if (comparableCandidate === comparableBase) return ".";
@@ -73122,21 +73322,21 @@ function normalizeContextSeparators(lines) {
73122
73322
  return normalized;
73123
73323
  }
73124
73324
  function parsedFilePath(line, mode, pathClass) {
73125
- if (line.kind === "record") return normalize$1(line.filePath);
73325
+ if (line.kind === "record") return normalize(line.filePath);
73126
73326
  if (line.kind === "separator") return void 0;
73127
73327
  const text = line.text;
73128
- if (mode === "files_with_matches") return normalize$1(text);
73328
+ if (mode === "files_with_matches") return normalize(text);
73129
73329
  if (mode === "count_matches") {
73130
73330
  const idx = text.lastIndexOf(":");
73131
- return idx > 0 ? normalize$1(text.slice(0, idx)) : normalize$1(text);
73331
+ return idx > 0 ? normalize(text.slice(0, idx)) : normalize(text);
73132
73332
  }
73133
73333
  return extractContentFilePath(text, pathClass);
73134
73334
  }
73135
73335
  function extractContentFilePath(line, pathClass) {
73136
73336
  const m = CONTENT_LINE_RE.exec(line);
73137
- if (m?.[1] !== void 0) return normalize$1(m[1]);
73337
+ if (m?.[1] !== void 0) return normalize(m[1]);
73138
73338
  const separatorIndex = noLineNumberContentSeparatorIndex(line, pathClass);
73139
- return separatorIndex > 0 ? normalize$1(line.slice(0, separatorIndex)) : void 0;
73339
+ return separatorIndex > 0 ? normalize(line.slice(0, separatorIndex)) : void 0;
73140
73340
  }
73141
73341
  function noLineNumberContentSeparatorIndex(line, pathClass) {
73142
73342
  const searchFrom = pathClass === "win32" && /^[A-Za-z]:/.test(line) ? 2 : 0;
@@ -75170,7 +75370,7 @@ function resolveEntrySource$1(source, location) {
75170
75370
  }
75171
75371
  function resolveLocalPath$1(input, workDir) {
75172
75372
  if (input === "~") return homedir();
75173
- if (input.startsWith("~/")) return join(homedir(), input.slice(2));
75373
+ if (input.startsWith("~/") || input.startsWith("~\\")) return join(homedir(), input.slice(2));
75174
75374
  return isAbsolute(input) ? input : resolve(workDir ?? process.cwd(), input);
75175
75375
  }
75176
75376
  function stringField$1(value, field) {
@@ -76404,7 +76604,7 @@ function splitSegments(value) {
76404
76604
  }
76405
76605
  //#endregion
76406
76606
  //#region ../../packages/agent-core/src/tools/builtin/shell/bash.md
76407
- var bash_default = "Execute a `{{ SHELL_NAME }}` command. Use this for shell semantics — pipes, env, processes, git, package managers, build/test runners, anything genuinely interactive or multi-step.\n\n**Translate these to a dedicated tool instead:**\n- `cat` / `head` / `tail` (known path) → `Read`\n- `sed` / `awk` (in-place edit) → `Edit`\n- `echo > file` / `cat <<EOF` → `Write`\n- `find` / recursive `ls` to locate files by name pattern → `Glob` (plain `ls <known-directory>` is fine for listing a directory)\n- `grep` / `rg` (search file contents) → `Grep`\n- `echo` / `printf` (talk to the user) → just output text directly\n\nThe dedicated tools render in the per-tool permission UI and keep raw stdout out of the conversation; that is why they are worth reaching for whenever one fits.\n\n**Output:**\nThe stdout and stderr will be combined and returned as a string. The output may be truncated if it is too long. If the command failed, the output will end with a `Command failed with exit code: N` line stating the non-zero exit code.\n\nIf `run_in_background=true`, the command will be started as a background task and this tool will return a task ID instead of waiting for command completion. When doing that, you must provide a short `description`. Background commands default to a {{ DEFAULT_BACKGROUND_TIMEOUT_S }}s timeout and `timeout` is capped at {{ MAX_BACKGROUND_TIMEOUT_S }}s; set `disable_timeout=true` only when the task should run without a timeout. You will be automatically notified when the task completes. Use `TaskOutput` for a non-blocking status/output snapshot, and only set `block=true` when you explicitly want to wait for completion. Use `TaskStop` only if the task must be cancelled. If a human user wants to inspect background tasks themselves, point them to the `/tasks` command, which opens an interactive panel; it has no subcommands.\n\n**Guidelines for safety and security:**\n- Each shell tool call will be executed in a fresh shell environment. The shell variables, current working directory changes, and the shell history is not preserved between calls.\n- The tool call will return after the command is finished. You shall not use this tool to execute an interactive command or a command that may run forever. For possibly long-running foreground commands, set the `timeout` argument in seconds. Foreground commands default to {{ DEFAULT_TIMEOUT_S }}s and allow up to {{ MAX_TIMEOUT_S }}s.\n- Avoid using `..` to access files or directories outside of the working directory.\n- Avoid modifying files outside of the working directory unless explicitly instructed to do so.\n- Never run commands that require superuser privileges unless explicitly instructed to do so.\n\n**Guidelines for efficiency:**\n- For multiple related commands, use `&&` to chain them in a single call, e.g. `cd /path && ls -la`\n- Use `;` to run commands sequentially regardless of success/failure\n- Use `||` for conditional execution (run second command only if first fails)\n- Use pipe operations (`|`) and redirections (`>`, `>>`) to chain input and output between commands\n- Always quote file paths containing spaces with double quotes (e.g., cd \"/path with spaces/\")\n- Compose multi-step logic in a single call with `if` / `case` / `for` / `while` control flows.\n- Prefer `run_in_background=true` for long-running builds, tests, watchers, or servers when you need the conversation to continue before the command finishes.\n\n**Commands available:**\nThe following common command categories are usually available. Availability still depends on the host, so when in doubt run `which <command>` first to confirm a command exists before relying on it.\n- Navigation and inspection: `ls`, `pwd`, `cd`, `stat`, `file`, `du`, `df`, `tree`\n- File and directory management: `cp`, `mv`, `rm`, `mkdir`, `touch`, `ln`, `chmod`, `chown`\n- Text and data processing: `wc`, `sort`, `uniq`, `cut`, `tr`, `diff`, `xargs`\n- Archives and compression: `tar`, `gzip`, `gunzip`, `zip`, `unzip`\n- Networking and transfer: `curl`, `wget`, `ping`, `ssh`, `scp`\n- Version control: `git`\n- Process and system: `ps`, `kill`, `top`, `env`, `date`, `uname`, `whoami`\n- Language and package toolchains: `node`, `npm`, `pnpm`, `yarn`, `python`, `pip` (use whichever the project actually relies on)\n";
76607
+ var bash_default = "Execute a `{{ SHELL_NAME }}` command. Use this for shell semantics — pipes, env, processes, git, package managers, build/test runners, anything genuinely interactive or multi-step.\n\n**Translate these to a dedicated tool instead:**\n- `cat` / `head` / `tail` (known path) → `Read`\n- `sed` / `awk` (in-place edit) → `Edit`\n- `echo > file` / `cat <<EOF` → `Write`\n- `find` / recursive `ls` to locate files by name pattern → `Glob` (plain `ls <known-directory>` is fine for listing a directory)\n- `grep` / `rg` (search file contents) → `Grep`\n- `echo` / `printf` (talk to the user) → just output text directly\n\nThe dedicated tools render in the per-tool permission UI and keep raw stdout out of the conversation; that is why they are worth reaching for whenever one fits.\n\n**Output:**\nThe stdout and stderr will be combined and returned as a string. The output may be truncated if it is too long. If the command failed, the output will end with a `Command failed with exit code: N` line stating the non-zero exit code.\n\nIf `run_in_background=true`, the command will be started as a background task and this tool will return a task ID instead of waiting for command completion. When doing that, you must provide a short `description`. Background commands default to a {{ DEFAULT_BACKGROUND_TIMEOUT_S }}s timeout and `timeout` is capped at {{ MAX_BACKGROUND_TIMEOUT_S }}s; set `disable_timeout=true` only when the task should run without a timeout. You will be automatically notified when the task completes. Use `TaskOutput` for a non-blocking status/output snapshot, and only set `block=true` when you explicitly want to wait for completion. Use `TaskStop` only if the task must be cancelled. If a human user wants to inspect background tasks themselves, point them to the `/tasks` command, which opens an interactive panel; it has no subcommands.\n\n**Guidelines for safety and security:**\n- Each shell tool call will be executed in a fresh shell environment. The shell variables, current working directory changes, and the shell history is not preserved between calls.\n- The tool call will return after the command is finished. You shall not use this tool to execute an interactive command or a command that may run forever. For possibly long-running foreground commands, set the `timeout` argument in seconds. Foreground commands default to {{ DEFAULT_TIMEOUT_S }}s and allow up to {{ MAX_TIMEOUT_S }}s.\n- Avoid using `..` to access files or directories outside of the working directory.\n- Avoid modifying files outside of the working directory unless explicitly instructed to do so.\n- Never run commands that require superuser privileges unless explicitly instructed to do so.\n\n**Guidelines for efficiency:**\n{%- if SHELL_IS_POWERSHELL %}\n- Chain with `;` to run commands sequentially. `&&` / `||` only exist from PowerShell 7 and are a parse error on Windows PowerShell 5.1, so do not use them; write `; if ($?) { … }` when the next step must run only if the previous one succeeded.\n- Filter and transform with pipelines (`Get-ChildItem | Where-Object …`); redirect with `>` / `>>`.\n- Quote paths containing spaces with single quotes, e.g. `Set-Location 'C:\\Program Files'`.\n- Read environment variables as `$env:NAME` (`$env:NAME = 'value'`), never `$NAME`.\n- Compose multi-step logic in a single call with `if` / `foreach` / `while` and `try` / `catch`.\n{%- else %}\n- For multiple related commands, use `&&` to chain them in a single call, e.g. `cd /path && ls -la`\n- Use `;` to run commands sequentially regardless of success/failure\n- Use `||` for conditional execution (run second command only if first fails)\n- Use pipe operations (`|`) and redirections (`>`, `>>`) to chain input and output between commands\n- Always quote file paths containing spaces with double quotes (e.g., cd \"/path with spaces/\")\n- Compose multi-step logic in a single call with `if` / `case` / `for` / `while` control flows.\n{%- endif %}\n- Prefer `run_in_background=true` for long-running builds, tests, watchers, or servers when you need the conversation to continue before the command finishes.\n\n**Commands available:**\nThe following common command categories are usually available. Availability still depends on the host, so when in doubt {% if SHELL_IS_POWERSHELL %}confirm a command exists with `Get-Command <name> -ErrorAction SilentlyContinue` before relying on it — several names below are cmdlet aliases (`ls` → `Get-ChildItem`, `rm` → `Remove-Item`), and POSIX-only entries such as `uname`, `chmod` and `kill` may be absent.{% else %}run `which <command>` first to confirm a command exists before relying on it.{% endif %}\n- Navigation and inspection: `ls`, `pwd`, `cd`, `stat`, `file`, `du`, `df`, `tree`\n- File and directory management: `cp`, `mv`, `rm`, `mkdir`, `touch`, `ln`, `chmod`, `chown`\n- Text and data processing: `wc`, `sort`, `uniq`, `cut`, `tr`, `diff`, `xargs`\n- Archives and compression: `tar`, `gzip`, `gunzip`, `zip`, `unzip`\n- Networking and transfer: `curl`, `wget`, `ping`, `ssh`, `scp`\n- Version control: `git`\n- Process and system: `ps`, `kill`, `top`, `env`, `date`, `uname`, `whoami`\n- Language and package toolchains: `node`, `npm`, `pnpm`, `yarn`, `python`, `pip` (use whichever the project actually relies on)\n";
76408
76608
  //#endregion
76409
76609
  //#region ../../packages/agent-core/src/tools/builtin/shell/background-tasks.ts
76410
76610
  /**
@@ -76466,13 +76666,27 @@ function drainCompletedBackgroundTasks() {
76466
76666
  /**
76467
76667
  * BashTool — execute shell commands.
76468
76668
  *
76469
- * Invokes bash (POSIX) according to an injected `Environment`. On Windows
76470
- * the shell is Git Bash; the path is resolved by `detectEnvironment`.
76669
+ * Invokes whichever shell an injected `Environment` selected, in that shell's
76670
+ * own dialect:
76671
+ *
76672
+ * - POSIX (`bash` / `sh`, including Git Bash on Windows) — `-c` with a
76673
+ * POSIX-quoted `cd … && …` script. Windows Git Bash additionally gets its
76674
+ * cwd converted to a `/c/…` path and its `>nul` redirects rewritten.
76675
+ * - PowerShell (`pwsh` / `powershell`, the Windows fallback when no Git Bash
76676
+ * is installed) — the interpreter's own `-NoProfile -NonInteractive
76677
+ * -Command` switches, the working directory set with `Set-Location
76678
+ * -LiteralPath`, the path left in its native `C:\…` form, its own
76679
+ * self-protection preamble (the bash shims are a parse error here), and no
76680
+ * bash syntax anywhere.
76681
+ *
76682
+ * The switches come from `Environment.shellArgs`; the dialect from
76683
+ * `Environment.shellName`. Neither is re-derived here, so a host whose probe
76684
+ * picked a fallback shell is actually driven with that shell.
76471
76685
  *
76472
76686
  * Dependencies injected via constructor:
76473
76687
  * - `Jian` — shell execution abstraction (exec / execWithEnv)
76474
76688
  * - `cwd` — default working directory for commands
76475
- * - `Environment` — cross-platform probe (shellName / shellPath)
76689
+ * - `Environment` — cross-platform probe (shellName / shellPath / shellArgs)
76476
76690
  * - `BackgroundProcessManager?` — optional: required iff run_in_background=true
76477
76691
  *
76478
76692
  * Execution goes through Jian, never directly via node:child_process.
@@ -76496,6 +76710,28 @@ const SIGTERM_GRACE_MS = 5e3;
76496
76710
  /** After the process exits, how long to keep draining stdout/stderr before
76497
76711
  * declaring completion (grandchildren may hold the pipe open). */
76498
76712
  const STREAM_GRACE_MS = 500;
76713
+ /**
76714
+ * Which dialect `env` selected. `shellName` is the probe's answer to "which
76715
+ * interpreter is this", so it decides the dialect; `osKind` does not, because
76716
+ * Windows carries either dialect depending on whether Git Bash was found.
76717
+ */
76718
+ function shellKindOf(env) {
76719
+ return env.shellName === "pwsh" || env.shellName === "powershell" ? "powershell" : "posix";
76720
+ }
76721
+ /**
76722
+ * Non-interactive script switches, used only when the `Environment` pins a
76723
+ * shell path without args. Mirrors what `detectEnvironment` puts in
76724
+ * `Environment.shellArgs`, so a hand-built environment behaves like a probed
76725
+ * one: `-c` for bash/sh, and no profile / no prompt for PowerShell.
76726
+ */
76727
+ const DEFAULT_SHELL_ARGS = {
76728
+ posix: ["-c"],
76729
+ powershell: [
76730
+ "-NoProfile",
76731
+ "-NonInteractive",
76732
+ "-Command"
76733
+ ]
76734
+ };
76499
76735
  const BashInputSchema = z.object({
76500
76736
  command: z.string().min(1, "Command cannot be empty.").describe("The command to execute."),
76501
76737
  cwd: z.string().optional().describe("The working directory in which to run the command. When omitted, the command runs in the session's working directory."),
@@ -76535,14 +76771,25 @@ function isValidTimeoutValue(timeout, isBackground) {
76535
76771
  function normalizeTimeoutMs(timeout, isBackground) {
76536
76772
  return Math.min(timeout ?? (isBackground ? DEFAULT_BACKGROUND_TIMEOUT_S : DEFAULT_TIMEOUT_S), timeoutCapS(isBackground)) * MS_PER_SECOND;
76537
76773
  }
76538
- function renderBashDescription(shellName) {
76774
+ function renderBashDescription(shellName, isPowerShell) {
76539
76775
  return renderPrompt(bash_default, {
76540
76776
  ...SHELL_TIMEOUT_VARS,
76541
- SHELL_NAME: shellName
76777
+ SHELL_NAME: shellName,
76778
+ SHELL_IS_POWERSHELL: isPowerShell
76542
76779
  });
76543
76780
  }
76781
+ /**
76782
+ * The description with its background-mode paragraph replaced by a refusal.
76783
+ *
76784
+ * The replacements below match literal template text, and the first one anchors
76785
+ * on a blank line (`\n\n`). A checkout whose line endings are CRLF — which is
76786
+ * what Windows runners get from Git by default — renders that blank line as
76787
+ * `\r\n\r\n`, so the anchor never matches and the background paragraph survives
76788
+ * into a tool that cannot run background commands. Normalise the line endings
76789
+ * first: the result must not depend on how the template file was checked out.
76790
+ */
76544
76791
  function withoutBackgroundDescription(description) {
76545
- return description.replace(/\n\nIf `run_in_background=true`,[\s\S]*?point them to the `\/tasks` command, which opens an interactive panel; it has no subcommands\./, "\n\nBackground execution is disabled for this agent. Do not set `run_in_background=true`.").replace(` For possibly long-running foreground commands, set the \`timeout\` argument in seconds. Foreground commands default to ${String(DEFAULT_TIMEOUT_S)}s and allow up to ${String(MAX_TIMEOUT_S)}s.`, ` For possibly long-running commands, set the \`timeout\` argument in seconds. The default is ${String(DEFAULT_TIMEOUT_S)}s; foreground commands allow up to ${String(MAX_TIMEOUT_S)}s.`).replace(/\n- Prefer `run_in_background=true`[\s\S]*?conversation to continue before the command finishes\./, "\n- Do not set `run_in_background=true`; background task management tools are not available.");
76792
+ return description.replaceAll("\r\n", "\n").replace(/\n\nIf `run_in_background=true`,[\s\S]*?point them to the `\/tasks` command, which opens an interactive panel; it has no subcommands\./, "\n\nBackground execution is disabled for this agent. Do not set `run_in_background=true`.").replace(` For possibly long-running foreground commands, set the \`timeout\` argument in seconds. Foreground commands default to ${String(DEFAULT_TIMEOUT_S)}s and allow up to ${String(MAX_TIMEOUT_S)}s.`, ` For possibly long-running commands, set the \`timeout\` argument in seconds. The default is ${String(DEFAULT_TIMEOUT_S)}s; foreground commands allow up to ${String(MAX_TIMEOUT_S)}s.`).replace(/\n- Prefer `run_in_background=true`[\s\S]*?conversation to continue before the command finishes\./, "\n- Do not set `run_in_background=true`; background task management tools are not available.");
76546
76793
  }
76547
76794
  function rejectDangerousCommand(pattern, hint) {
76548
76795
  return {
@@ -76605,19 +76852,74 @@ function validateCommand(command, isWindows) {
76605
76852
  if (/\b(killall|pkill)\b.*\b(node|scream)/i.test(cmd)) return rejectDangerousCommand("killall/pkill node", "Use 'kill <pid>' with a specific PID, or use the server's own stop command.");
76606
76853
  if (isWindows) {
76607
76854
  if (/\btasklist\b/.test(cmd) && /\bgrep\b.*\b(node|scream)/i.test(cmd) && /\b(taskkill|tskill)\b/.test(cmd)) return rejectDangerousCommand("tasklist | grep node | taskkill pipeline", "Use 'taskkill /PID <pid>' with the specific preview server PID. First run 'tasklist | grep -i node' to find the exact PID, then kill only that one.");
76608
- if (/\btaskkill\b.*\/IM\s+node/i.test(cmd)) return rejectDangerousCommand("taskkill /IM node.exe", "Use 'taskkill /PID <pid>' with a specific PID instead of killing by image name.");
76609
76855
  if (/\bwmic\s+process\s+.*where\s+.*name.*=.*node/i.test(cmd) && /\bdelete\b/i.test(cmd)) return rejectDangerousCommand("wmic process delete", "Use 'taskkill /PID <pid>' with a specific PID instead.");
76610
- if (/\bstop-process\b.*-Name\s+node/i.test(cmd)) return rejectDangerousCommand("Stop-Process -Name node", "Use 'Stop-Process -Id <pid>' with a specific PID instead.");
76856
+ const segments = cmd.split(/[;&|\n]/);
76857
+ for (const segment of segments) {
76858
+ if (/\bstop-process\b/i.test(segment) && /-(?:N|Na|Nam|Name)(?::|\s|$)/i.test(segment)) return rejectDangerousCommand("Stop-Process by name", "Use 'Stop-Process -Id <pid>' with a specific PID instead of killing by process name.");
76859
+ if (/\bstop-process\b/i.test(segment) && /-(?:I|In|Inp|Inpu|Input|InputO|InputOb|InputObj|InputObje|InputObjec|InputObject)(?::|\s|$)/i.test(segment)) return rejectDangerousCommand("Stop-Process -InputObject", "Use 'Stop-Process -Id <pid>' with a specific PID instead of passing a process object.");
76860
+ if (/\b(taskkill|tskill)\b/i.test(segment) && /[/-]IM(?::|\s|$)/i.test(segment)) return rejectDangerousCommand("taskkill by image name", "Use 'taskkill /PID <pid>' with a specific PID instead of killing by image name.");
76861
+ }
76862
+ for (let index = 0; index < segments.length; index += 1) {
76863
+ const segment = segments[index];
76864
+ if (/\bget-process\b/i.test(segment) && /-(?:N|Na|Nam|Name)(?::|\s|$)/i.test(segment) && segments.slice(index + 1).some((later) => /\bstop-process\b/i.test(later))) return rejectDangerousCommand("Get-Process by name piped into Stop-Process", "Use 'Stop-Process -Id <pid>' with a specific PID instead of killing by process name.");
76865
+ }
76866
+ const bypassesShadow = /taskkill\.exe|Microsoft\.PowerShell\.Management\\Stop-Process|\[(?:System\.)?Diagnostics\.Process\]/i;
76867
+ const selfPidOperand = /%SCREAM_PID%|\$env:SCREAM_PID|\$\{PID\}|\$PID\b/i;
76868
+ for (const segment of segments) if (bypassesShadow.test(segment) && selfPidOperand.test(segment)) return rejectDangerousCommand("kill targeting this run through a shadow-bypassing command", "Use a specific pid that is not this run's own — never $PID or $env:SCREAM_PID (%SCREAM_PID%).");
76611
76869
  } else {
76612
76870
  if (/\bps\b/.test(cmd) && /\bgrep\b.*\b(node|scream)/i.test(cmd) && /\bxargs\s+kill\b/.test(cmd)) return rejectDangerousCommand("ps | grep node | xargs kill pipeline", "Use 'kill <pid>' with a specific PID instead.");
76613
76871
  if (/\bpgrep\b.*\b(node|scream)/i.test(cmd) && /\bxargs\s+kill\b/.test(cmd)) return rejectDangerousCommand("pgrep node | xargs kill", "Use 'kill <pid>' with a specific PID instead.");
76614
76872
  }
76615
76873
  return null;
76616
76874
  }
76617
- function buildSelfProtectionPreamble(isWindows) {
76618
- if (isWindows) return "_SCREAM_CHECK(){ for _a in \"$@\";do [ \"$_a\" = \"$SCREAM_PID\" ]&&{ echo \"Scream Code self-protection: refusing to kill itself (pid $SCREAM_PID). Use a specific non-Scream PID.\">&2;return 1;};done;return 0;};kill(){ _SCREAM_CHECK \"$@\"||return 1;command kill \"$@\";};pkill(){ echo \"Scream Code self-protection: pkill blocked. Use kill <pid>.\">&2;return 1;};taskkill(){ _SCREAM_CHECK \"$@\"||return 1;command taskkill \"$@\";};tskill(){ _SCREAM_CHECK \"$@\"||return 1;command tskill \"$@\";};";
76875
+ function buildSelfProtectionPreamble(isWindowsBash) {
76876
+ if (isWindowsBash) return "_SCREAM_CHECK(){ for _a in \"$@\";do [ \"$_a\" = \"$SCREAM_PID\" ]&&{ echo \"Scream Code self-protection: refusing to kill itself (pid $SCREAM_PID). Use a specific non-Scream PID.\">&2;return 1;};done;return 0;};kill(){ _SCREAM_CHECK \"$@\"||return 1;command kill \"$@\";};pkill(){ echo \"Scream Code self-protection: pkill blocked. Use kill <pid>.\">&2;return 1;};taskkill(){ _SCREAM_CHECK \"$@\"||return 1;command taskkill \"$@\";};tskill(){ _SCREAM_CHECK \"$@\"||return 1;command tskill \"$@\";};";
76619
76877
  return "_SCREAM_CHECK(){ for _a in \"$@\";do [ \"$_a\" = \"$SCREAM_PID\" ]||[ \"$_a\" = \"-$SCREAM_PID\" ]&&{ echo \"Scream Code self-protection: refusing to kill itself (pid $SCREAM_PID). Use a specific non-Scream PID.\">&2;return 1;};done;return 0;};kill(){ _SCREAM_CHECK \"$@\"||return 1;command kill \"$@\";};pkill(){ echo \"Scream Code self-protection: pkill blocked. Use kill <pid>.\">&2;return 1;};killall(){ echo \"Scream Code self-protection: killall blocked. Use kill <pid>.\">&2;return 1;};";
76620
76878
  }
76879
+ /**
76880
+ * The PowerShell self-protection preamble — the dialect's counterpart of
76881
+ * {@link buildSelfProtectionPreamble}, which PowerShell cannot run (a bash
76882
+ * function definition is a parse error there).
76883
+ *
76884
+ * `SCREAM_PID` names the process that started this shell and is inherited
76885
+ * through the Jian environment allowlist; `$PID` is the shell itself. Killing
76886
+ * either — by pid, by process name (`Stop-Process -Name nod*` reaches the host
76887
+ * through a wildcard no pid comparison can catch), or by handing the cmdlet the
76888
+ * process object itself — detaches the tool from the command it is running, so
76889
+ * the three commands that can do it are shadowed:
76890
+ *
76891
+ * - `Stop-Process`, the cmdlet;
76892
+ * - `kill` — its built-in alias. An alias outranks a function in PowerShell's
76893
+ * resolution order, so the alias is repointed instead of redefined;
76894
+ * - `taskkill`, an executable, which a function of the same name does shadow
76895
+ * (a function outranks an application).
76896
+ *
76897
+ * The real commands stay reachable by their module-qualified and `.exe` names,
76898
+ * which no shadow can capture.
76899
+ *
76900
+ * Each shadow is a *simple* function on purpose: only then does PowerShell
76901
+ * collect an unbound `-Id 1234` into `$args`, and `@args` splats those
76902
+ * parameters back onto the real command, so every case except the guarded pid
76903
+ * and the refused by-name, `-InputObject` and process-object forms — including
76904
+ * `-WhatIf`, `-Force` and pipeline input — behaves as before. A refusal
76905
+ * `throw`s rather than `exit`s: it is an ordinary terminating error, so the
76906
+ * message lands on stderr and the shell exits non-zero, exactly as the bash
76907
+ * shims behave.
76908
+ *
76909
+ * Written for Windows PowerShell 5.1 and PowerShell 7 alike: one line of
76910
+ * `;`-separated statements, no `&&`, no ternary, no null-coalescing, no class
76911
+ * syntax — 5.1 parses none of those.
76912
+ */
76913
+ function buildPowerShellSelfProtectionPreamble() {
76914
+ return [
76915
+ "function _ScreamGuardPid([object[]] $Ids) { $guarded = @([string] $PID, [string] $env:SCREAM_PID); foreach ($id in $Ids) { foreach ($one in @($id)) { if ($null -eq $one) { continue }; if ($guarded -contains ([string] $one)) { throw \"Scream Code self-protection: refusing to kill itself (pid $one). Use a specific non-Scream PID.\" } } } }",
76916
+ "function _ScreamGuardByName([object[]] $Arguments) { foreach ($one in $Arguments) { $text = [string] $one; if ($text -match '^-(N|Na|Nam|Name)(:|$)') { throw \"Scream Code self-protection: refusing to kill by process name ($text). Use a specific non-Scream PID.\" } if ($text -match '^[/-]IM(:|$)' -or $text -match 'IMAGENAME') { throw \"Scream Code self-protection: refusing to kill by image name ($text). Use a specific non-Scream PID.\" } } }",
76917
+ "function _ScreamGuardInputObject([object[]] $Arguments) { foreach ($one in $Arguments) { $text = [string] $one; if ($text -match '^-(I|In|Inp|Inpu|Input|InputO|InputOb|InputObj|InputObje|InputObjec|InputObject)(:|$)') { throw \"Scream Code self-protection: refusing to kill via -InputObject, whose operand cannot be inspected. Use a specific non-Scream PID.\" } if ($one -is [System.Diagnostics.Process]) { throw \"Scream Code self-protection: refusing to kill a process object, whose target cannot be inspected. Use a specific non-Scream PID.\" } } }",
76918
+ "function Stop-Process { _ScreamGuardPid $args; _ScreamGuardByName $args; _ScreamGuardInputObject $args; $incoming = @($input | Where-Object { $null -ne $_ }); if ($incoming.Count -gt 0) { _ScreamGuardPid ($incoming | ForEach-Object { $_.Id }); Microsoft.PowerShell.Management\\Stop-Process -InputObject $incoming @args } else { Microsoft.PowerShell.Management\\Stop-Process @args } }",
76919
+ "function taskkill { _ScreamGuardPid $args; _ScreamGuardByName $args; taskkill.exe @args }",
76920
+ "Set-Alias -Name kill -Value Stop-Process -Force"
76921
+ ].join("; ");
76922
+ }
76621
76923
  var BashTool = class {
76622
76924
  jian;
76623
76925
  cwd;
@@ -76625,17 +76927,24 @@ var BashTool = class {
76625
76927
  name = "Bash";
76626
76928
  description;
76627
76929
  parameters = toInputJsonSchema(BashInputSchema);
76930
+ /** Windows Git Bash — a POSIX shell running on Windows. */
76628
76931
  isWindowsBash;
76932
+ /** This host is Windows. Independent of which dialect its shell speaks. */
76933
+ isWindows;
76934
+ /** The dialect the probed shell speaks. */
76935
+ shellKind;
76629
76936
  allowBackground;
76630
76937
  availableTools;
76631
76938
  constructor(jian, cwd, backgroundManager, options) {
76632
76939
  this.jian = jian;
76633
76940
  this.cwd = cwd;
76634
76941
  this.backgroundManager = backgroundManager;
76635
- this.isWindowsBash = this.jian.osEnv.osKind === "Windows";
76942
+ this.isWindows = this.jian.osEnv.osKind === "Windows";
76943
+ this.shellKind = shellKindOf(this.jian.osEnv);
76944
+ this.isWindowsBash = this.isWindows && this.shellKind === "posix";
76636
76945
  this.allowBackground = options?.allowBackground ?? this.backgroundManager !== void 0;
76637
76946
  this.availableTools = options?.availableTools ?? /* @__PURE__ */ new Set();
76638
- const rendered = renderBashDescription(this.jian.osEnv.shellName);
76947
+ const rendered = renderBashDescription(this.jian.osEnv.shellName, this.shellKind === "powershell");
76639
76948
  this.description = this.allowBackground ? rendered : withoutBackgroundDescription(rendered);
76640
76949
  }
76641
76950
  resolveExecution(args) {
@@ -76655,12 +76964,10 @@ var BashTool = class {
76655
76964
  };
76656
76965
  }
76657
76966
  spawn(effectiveCwd, command) {
76658
- const shellCwd = this.isWindowsBash ? windowsPathToPosixPath(effectiveCwd) : effectiveCwd;
76659
- const preamble = buildSelfProtectionPreamble(this.isWindowsBash);
76660
76967
  const shellArgs = [
76661
76968
  this.jian.osEnv.shellPath,
76662
- "-c",
76663
- `cd ${shellQuote(shellCwd)} && ${preamble}\n${command}`
76969
+ ...this.jian.osEnv.shellArgs ?? DEFAULT_SHELL_ARGS[this.shellKind],
76970
+ this.scriptFor(effectiveCwd, command)
76664
76971
  ];
76665
76972
  const mergedEnv = {
76666
76973
  NO_COLOR: "1",
@@ -76671,6 +76978,11 @@ var BashTool = class {
76671
76978
  };
76672
76979
  return this.jian.execWithEnv(shellArgs, mergedEnv);
76673
76980
  }
76981
+ /** The script body handed to the selected interpreter — one dialect each. */
76982
+ scriptFor(effectiveCwd, command) {
76983
+ if (this.shellKind === "powershell") return buildPowerShellScript(effectiveCwd, command);
76984
+ return buildPosixScript(this.isWindowsBash ? windowsPathToPosixPath(effectiveCwd) : effectiveCwd, buildSelfProtectionPreamble(this.isWindowsBash), command);
76985
+ }
76674
76986
  async execution(args, ctx) {
76675
76987
  const { signal, onUpdate } = ctx;
76676
76988
  const completedBg = drainCompletedBackgroundTasks();
@@ -76683,7 +76995,7 @@ var BashTool = class {
76683
76995
  isError: true,
76684
76996
  output: bgPrefix + "Command cannot be empty."
76685
76997
  };
76686
- const validationError = validateCommand(args.command, this.isWindowsBash);
76998
+ const validationError = validateCommand(args.command, this.isWindows);
76687
76999
  if (validationError !== null) return validationError;
76688
77000
  const interception = checkBashInterception(args.command, this.availableTools);
76689
77001
  if (interception !== null) return interception;
@@ -76815,7 +77127,7 @@ var BashTool = class {
76815
77127
  const isError = exitCode !== 0;
76816
77128
  if (isError && builder.nChars === 0) builder.write(`Process exited with code ${String(exitCode)}`);
76817
77129
  if (!isError) return builder.ok("Command executed successfully.");
76818
- const hint = looksLikeCommandNotFound(command, builder.toString()) ? `\\n${commandNotFoundHint()}` : "";
77130
+ const hint = looksLikeCommandNotFound(command, builder.toString()) ? `\n${commandNotFoundHint()}` : "";
76819
77131
  return builder.error(`Command failed with exit code: ${String(exitCode)}.${hint}`, { brief: `Failed with exit code: ${String(exitCode)}` });
76820
77132
  } catch (error) {
76821
77133
  return {
@@ -76932,6 +77244,42 @@ async function readStreamIntoBuilder(stream, builder, onChunk) {
76932
77244
  function shellQuote(s) {
76933
77245
  return `'${s.replaceAll("'", "'\\''")}'`;
76934
77246
  }
77247
+ /**
77248
+ * PowerShell's literal string — single quotes, with an embedded quote doubled.
77249
+ * `$`, backticks and `\` are literal inside it, so a Windows path needs no
77250
+ * further escaping.
77251
+ */
77252
+ function powershellQuote(s) {
77253
+ return `'${s.replaceAll("'", "''")}'`;
77254
+ }
77255
+ /**
77256
+ * The POSIX script body: set the working directory, install the
77257
+ * self-protection shims, then run the command. `cd … && …` keeps a missing
77258
+ * directory from running the command somewhere else.
77259
+ */
77260
+ function buildPosixScript(shellCwd, preamble, command) {
77261
+ return `cd ${shellQuote(shellCwd)} && ${preamble}\n${command}`;
77262
+ }
77263
+ /**
77264
+ * The PowerShell script body.
77265
+ *
77266
+ * No POSIX cwd conversion and no bash syntax at all: the cwd is passed in its
77267
+ * native `C:\…` form, and the working directory is set with the cmdlet
77268
+ * (`Set-Location -LiteralPath`) instead of bash's `cd` builtin — the
77269
+ * `-LiteralPath` switch keeps `[` and `]` in a path from being read as
77270
+ * wildcards. `-ErrorAction Stop` turns a missing directory into a terminating
77271
+ * error, so the command never runs from the process default directory; that is
77272
+ * the fail-fast behaviour the POSIX branch gets from `cd … && …`. A `;`
77273
+ * separates the statements rather than `&&`, which only exists from PowerShell
77274
+ * 7 and would be a parse error on the Windows PowerShell 5.1 the fallback must
77275
+ * also serve.
77276
+ *
77277
+ * The self-protection preamble sits between the two, so the command always runs
77278
+ * with the guards in place.
77279
+ */
77280
+ function buildPowerShellScript(effectiveCwd, command) {
77281
+ return `Set-Location -LiteralPath ${powershellQuote(effectiveCwd)} -ErrorAction Stop; ${buildPowerShellSelfProtectionPreamble()}; ${command}`;
77282
+ }
76935
77283
  function windowsPathToPosixPath(path) {
76936
77284
  if (path.startsWith("\\\\")) return path.replaceAll("\\", "/");
76937
77285
  const driveMatch = /^([A-Za-z]):(?:[\\/]|$)/.exec(path);
@@ -76947,20 +77295,6 @@ function rewriteWindowsNullRedirect(command) {
76947
77295
  return command.replace(WINDOWS_NUL_REDIRECT, "$1/dev/null");
76948
77296
  }
76949
77297
  //#endregion
76950
- //#region ../../packages/agent-core/src/config/path.ts
76951
- function resolveScreamHome(homeDir) {
76952
- return homeDir ?? process.env["SCREAM_CODE_HOME"] ?? join$1(homedir(), ".scream-code");
76953
- }
76954
- function resolveConfigPath(input) {
76955
- return input.configPath ?? join$1(resolveScreamHome(input.homeDir), "config.toml");
76956
- }
76957
- function ensureScreamHome(homeDir) {
76958
- mkdirSync(homeDir, {
76959
- recursive: true,
76960
- mode: 448
76961
- });
76962
- }
76963
- //#endregion
76964
77298
  //#region ../../packages/agent-core/src/mcp/config-loader.ts
76965
77299
  const McpJsonFileSchema = z.object({ mcpServers: z.record(z.string(), McpServerConfigSchema).default({}) });
76966
77300
  /** Maximum number of parent directories to walk when discovering mcp.json. */
@@ -78034,6 +78368,52 @@ var EventSubscriptionBus = class {
78034
78368
  /** Rejection sink for async subscribers: their failures are deliberately dropped. */
78035
78369
  function noop$1() {}
78036
78370
  //#endregion
78371
+ //#region ../../packages/agent-core/src/agent/context/identity.ts
78372
+ /**
78373
+ * Determines whether a context message counts as a "user prompt" for undo
78374
+ * anchoring. Regular user messages and user-triggered skill activations both
78375
+ * count; injections, system reminders, scheduled notifications, hook results
78376
+ * and model-triggered skills don't.
78377
+ *
78378
+ * The bar is deliberately lower than {@link isUserAuthoredMessage}: this asks
78379
+ * whether the *user acted*, not whether the user wrote the text. A slash
78380
+ * command the user typed starts a turn the user expects `/undo` to reverse,
78381
+ * even though the message body is a rendered skill prompt.
78382
+ */
78383
+ function isRealUserPrompt(message) {
78384
+ if (message.role !== "user") return false;
78385
+ const origin = message.origin;
78386
+ if (origin === void 0 || origin.kind === "user") return true;
78387
+ if (origin.kind === "skill_activation") return origin.trigger === "user-slash";
78388
+ return false;
78389
+ }
78390
+ /**
78391
+ * Determines whether a context message was authored by the user as
78392
+ * conversation content — a direct user prompt, carrying
78393
+ * `origin.kind === 'user'`.
78394
+ *
78395
+ * Strictly narrower than {@link isRealUserPrompt} on both axes that matter:
78396
+ *
78397
+ * - a user-triggered skill activation carries `origin.kind ===
78398
+ * 'skill_activation'`. The user acted, but the body is the rendered skill
78399
+ * prompt, not the user's own words: concatenating it into an adjacent user
78400
+ * message, or quoting it back as "the user's request", would misattribute
78401
+ * generated text to the user;
78402
+ * - a message with no `origin` is not user-authored. Every message appended
78403
+ * through `ContextMemory.appendUserMessage` carries one, so a missing
78404
+ * origin means an internal producer wrote the message and its authorship
78405
+ * cannot be claimed for the user.
78406
+ *
78407
+ * Use this where the question is "are these the user's own words?" — merging
78408
+ * adjacent user messages for the provider, recovering the most recent user
78409
+ * request, resetting a cadence on genuine user input. Use
78410
+ * {@link isRealUserPrompt} where the question is "does this message start a
78411
+ * user turn the user could undo?".
78412
+ */
78413
+ function isUserAuthoredMessage(message) {
78414
+ return message.role === "user" && message.origin?.kind === "user";
78415
+ }
78416
+ //#endregion
78037
78417
  //#region ../../packages/agent-core/src/agent/context/projector.ts
78038
78418
  /** Synthetic error text used when a tool result is missing and must be
78039
78419
  * filled in so the provider accepts the message sequence. */
@@ -78106,17 +78486,16 @@ function makeSyntheticToolResult(toolCallId) {
78106
78486
  };
78107
78487
  }
78108
78488
  /**
78109
- * Drops a trailing open tool exchange from a projected message list: when the
78110
- * last assistant message carries tool calls whose results never arrived and
78111
- * nothing follows, truncate from that batch so the compacted history does not
78112
- * carry an unterminated exchange (which would be rejected or, if synthesized,
78113
- * pollute the compaction prompt).
78489
+ * Merges runs of adjacent user-authored messages into one, so the provider
78490
+ * never receives consecutive user-role messages. Only the user's own words
78491
+ * merge: an injection, a rendered skill prompt or a scheduled notification
78492
+ * keeps its own boundary even when it sits next to a real prompt.
78114
78493
  */
78115
78494
  function mergeAdjacentUserMessages(history, _onAnomaly) {
78116
78495
  const out = [];
78117
78496
  for (const message of history) {
78118
78497
  const previous = out.at(-1);
78119
- if (canMergeUserMessage(message) && previous !== void 0 && canMergeUserMessage(previous)) {
78498
+ if (isUserAuthoredMessage(message) && previous !== void 0 && isUserAuthoredMessage(previous)) {
78120
78499
  out[out.length - 1] = mergeTwoUserMessages(previous, message);
78121
78500
  continue;
78122
78501
  }
@@ -78124,9 +78503,6 @@ function mergeAdjacentUserMessages(history, _onAnomaly) {
78124
78503
  }
78125
78504
  return out.map(stripContextMetadata);
78126
78505
  }
78127
- function canMergeUserMessage(message) {
78128
- return message.role === "user" && message.origin?.kind === "user";
78129
- }
78130
78506
  function mergeTwoUserMessages(a, b) {
78131
78507
  const aText = extractTextOnly(a);
78132
78508
  const bText = extractTextOnly(b);
@@ -78942,13 +79318,13 @@ var FullCompaction = class {
78942
79318
  sessionId
78943
79319
  });
78944
79320
  }
78945
- /** Build a low-confidence fallback memo from the most recent real user
78946
- * message in the compacted history (origin.kind === 'user'), so an ongoing
79321
+ /** Build a low-confidence fallback memo from the most recent user-authored
79322
+ * message in the compacted history (`isUserAuthoredMessage`), so an ongoing
78947
79323
  * task is not lost when the summary omits the memory-memo section. */
78948
79324
  buildFallbackMemo(messages) {
78949
79325
  for (let i = messages.length - 1; i >= 0; i--) {
78950
79326
  const msg = messages[i];
78951
- if (msg === void 0 || msg.role !== "user" || msg.origin?.kind !== "user") continue;
79327
+ if (msg === void 0 || !isUserAuthoredMessage(msg)) continue;
78952
79328
  const text = msg.content.filter((p) => p.type === "text").map((p) => p.text).join("").trim();
78953
79329
  if (text.length === 0) continue;
78954
79330
  return createMemoryMemo({
@@ -81076,18 +81452,6 @@ function truncateContentParts(parts) {
81076
81452
  function isEmptyOutputText(output) {
81077
81453
  return output.length === 0 || output.trim() === TOOL_OUTPUT_EMPTY_TEXT;
81078
81454
  }
81079
- /**
81080
- * Determines whether a context message counts as a "user prompt" for undo
81081
- * anchoring. Regular user messages and user-triggered skill activations
81082
- * both count; injections, system reminders, and model-triggered skills don't.
81083
- */
81084
- function isRealUserPrompt(message) {
81085
- if (message.role !== "user") return false;
81086
- const origin = message.origin;
81087
- if (origin === void 0 || origin.kind === "user") return true;
81088
- if (origin.kind === "skill_activation") return origin.trigger === "user-slash";
81089
- return false;
81090
- }
81091
81455
  //#endregion
81092
81456
  //#region ../../packages/agent-core/src/agent/goal/index.ts
81093
81457
  /**
@@ -81502,6 +81866,1183 @@ function normalizeCompletionCriterion(value) {
81502
81866
  return trimmed?.length ? trimmed : void 0;
81503
81867
  }
81504
81868
  //#endregion
81869
+ //#region ../../packages/jian/src/errors.ts
81870
+ /**
81871
+ * Base error class for the jian package.
81872
+ */
81873
+ var JianError = class extends Error {
81874
+ constructor(message) {
81875
+ super(message);
81876
+ this.name = "JianError";
81877
+ }
81878
+ };
81879
+ /**
81880
+ * Equivalent to Python's FileExistsError — indicates a file or directory already exists.
81881
+ */
81882
+ var JianFileExistsError = class extends JianError {
81883
+ constructor(message) {
81884
+ super(message);
81885
+ this.name = "JianFileExistsError";
81886
+ }
81887
+ };
81888
+ /**
81889
+ * Thrown by `detectEnvironment` on Windows when neither Git Bash nor
81890
+ * PowerShell can be located. Carries the list of paths that were probed so
81891
+ * callers can include them in install hints.
81892
+ */
81893
+ var JianShellNotFoundError = class extends JianError {
81894
+ constructor(message) {
81895
+ super(message);
81896
+ this.name = "JianShellNotFoundError";
81897
+ }
81898
+ };
81899
+ /**
81900
+ * Thrown by `LocalJian` when a file operation would resolve outside the
81901
+ * instance's configured root directory. This is a last-line-of-defense guard;
81902
+ * higher-level path policies should still validate user-supplied paths first.
81903
+ */
81904
+ var JianPathOutsideRootError = class extends JianError {
81905
+ path;
81906
+ rootDir;
81907
+ constructor(message, path, rootDir) {
81908
+ super(message);
81909
+ this.path = path;
81910
+ this.rootDir = rootDir;
81911
+ this.name = "JianPathOutsideRootError";
81912
+ }
81913
+ };
81914
+ /** Thrown when process execution fails (spawn error, ENOENT, EACCES). */
81915
+ var JianExecError = class extends JianError {
81916
+ command;
81917
+ code;
81918
+ constructor(message, command, code) {
81919
+ super(message);
81920
+ this.command = command;
81921
+ this.code = code;
81922
+ this.name = "JianExecError";
81923
+ }
81924
+ };
81925
+ //#endregion
81926
+ //#region ../../packages/jian/src/environment.ts
81927
+ /**
81928
+ * Environment — cross-platform probe of OS / shell.
81929
+ *
81930
+ * Detection is a pure function of injected probes (`platform` / `arch` /
81931
+ * `release` / `env` / `isFile` / `findExecutable`) so the same suite runs
81932
+ * identically on any host OS. `detectEnvironmentFromNode()` bundles the
81933
+ * Node defaults for production callers.
81934
+ *
81935
+ * On Windows the probe prefers Git Bash (the canonical POSIX shell that ships
81936
+ * with Git for Windows) and falls back to PowerShell when Git Bash is absent,
81937
+ * so a Windows host without Git Bash still has a working execution channel.
81938
+ * `JianShellNotFoundError` is raised only when neither can be located; the SDK
81939
+ * layer can wrap that into a user-facing install hint. Set `SCREAM_SHELL_PATH`
81940
+ * to override the Git Bash lookup.
81941
+ */
81942
+ function resolveOsKind(platform) {
81943
+ switch (platform) {
81944
+ case "darwin": return "macOS";
81945
+ case "linux": return "Linux";
81946
+ case "win32": return "Windows";
81947
+ default: return platform;
81948
+ }
81949
+ }
81950
+ async function detectEnvironment(deps) {
81951
+ const osKind = resolveOsKind(deps.platform);
81952
+ const osArch = deps.arch;
81953
+ const osVersion = deps.release;
81954
+ if (deps.platform === "win32") return detectWindowsShell(deps, {
81955
+ osKind,
81956
+ osArch,
81957
+ osVersion
81958
+ });
81959
+ const candidates = [
81960
+ "/bin/bash",
81961
+ "/usr/bin/bash",
81962
+ "/usr/local/bin/bash"
81963
+ ];
81964
+ let found;
81965
+ for (const p of candidates) if (await deps.isFile(p)) {
81966
+ found = p;
81967
+ break;
81968
+ }
81969
+ if (found !== void 0) return {
81970
+ osKind,
81971
+ osArch,
81972
+ osVersion,
81973
+ shellName: "bash",
81974
+ shellPath: found,
81975
+ shellArgs: ["-c"]
81976
+ };
81977
+ return {
81978
+ osKind,
81979
+ osArch,
81980
+ osVersion,
81981
+ shellName: "sh",
81982
+ shellPath: "/bin/sh",
81983
+ shellArgs: ["-c"]
81984
+ };
81985
+ }
81986
+ /**
81987
+ * Windows shell selection: Git Bash first, PowerShell second, error last.
81988
+ *
81989
+ * Git Bash is preferred because the agent's command layer is POSIX-shaped
81990
+ * (single-quoted `-c` scripts, POSIX cwd conversion), so a Windows host with
81991
+ * Git for Windows behaves exactly like macOS / Linux. A host without it used to
81992
+ * have no execution channel at all; falling back to PowerShell at least keeps
81993
+ * the process-execution path available. `pwsh` (PowerShell 7+) wins over
81994
+ * `powershell.exe` (Windows PowerShell 5.1): it is the supported line and the
81995
+ * one accepting `-NoProfile -NonInteractive` unchanged.
81996
+ *
81997
+ * `checked` is shared with the Git Bash lookup so the "nothing found" error can
81998
+ * list every location that was probed.
81999
+ */
82000
+ async function detectWindowsShell(deps, base) {
82001
+ const checked = [];
82002
+ const gitBash = await locateWindowsGitBash(deps, checked);
82003
+ if (gitBash !== void 0) return {
82004
+ ...base,
82005
+ shellName: "bash",
82006
+ shellPath: gitBash,
82007
+ shellArgs: ["-c"]
82008
+ };
82009
+ const powerShell = await locateWindowsPowerShell(deps, checked);
82010
+ if (powerShell !== void 0) return {
82011
+ ...base,
82012
+ shellName: powerShell.name,
82013
+ shellPath: powerShell.path,
82014
+ shellArgs: [
82015
+ "-NoProfile",
82016
+ "-NonInteractive",
82017
+ "-Command"
82018
+ ]
82019
+ };
82020
+ throw new JianShellNotFoundError(`Neither Git Bash nor PowerShell was found on this Windows host. Install Git for Windows from https://gitforwindows.org/ (or set SCREAM_SHELL_PATH to a bash.exe), or install PowerShell. Checked: ${checked.join(", ")}.`);
82021
+ }
82022
+ async function locateWindowsGitBash(deps, checked) {
82023
+ const override = deps.env["SCREAM_SHELL_PATH"]?.trim();
82024
+ if (override !== void 0 && override.length > 0) {
82025
+ checked.push(override);
82026
+ if (await deps.isFile(override)) return override;
82027
+ }
82028
+ const gitExe = await deps.findExecutable("git.exe");
82029
+ if (gitExe !== void 0) {
82030
+ const inferred = inferGitBashFromGitExe(gitExe);
82031
+ if (inferred !== void 0) {
82032
+ checked.push(inferred);
82033
+ if (await deps.isFile(inferred)) return inferred;
82034
+ }
82035
+ const fellback = inferBashByWalkingUp(gitExe);
82036
+ if (fellback !== void 0) {
82037
+ checked.push(fellback);
82038
+ if (await deps.isFile(fellback)) return fellback;
82039
+ }
82040
+ }
82041
+ const bashExe = await deps.findExecutable("bash.exe");
82042
+ if (bashExe !== void 0) {
82043
+ checked.push(bashExe);
82044
+ return bashExe;
82045
+ }
82046
+ const candidates = [
82047
+ "C:\\Program Files\\Git\\bin\\bash.exe",
82048
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
82049
+ "C:\\msys64\\usr\\bin\\bash.exe",
82050
+ "C:\\cygwin64\\bin\\bash.exe"
82051
+ ];
82052
+ const localAppData = deps.env["LOCALAPPDATA"]?.trim();
82053
+ if (localAppData !== void 0 && localAppData.length > 0) candidates.push(`${localAppData}\\Programs\\Git\\bin\\bash.exe`);
82054
+ const userProfile = deps.env["USERPROFILE"]?.trim();
82055
+ if (userProfile !== void 0 && userProfile.length > 0) candidates.push(`${userProfile}\\scoop\\apps\\git\\current\\bin\\bash.exe`);
82056
+ for (const candidate of candidates) {
82057
+ checked.push(candidate);
82058
+ if (await deps.isFile(candidate)) return candidate;
82059
+ }
82060
+ }
82061
+ /** Locate a PowerShell to run scripts with when Git Bash is unavailable. */
82062
+ async function locateWindowsPowerShell(deps, checked) {
82063
+ for (const candidate of [{
82064
+ name: "pwsh",
82065
+ exe: "pwsh.exe"
82066
+ }, {
82067
+ name: "powershell",
82068
+ exe: "powershell.exe"
82069
+ }]) {
82070
+ const found = await deps.findExecutable(candidate.exe);
82071
+ if (found !== void 0) {
82072
+ checked.push(found);
82073
+ return {
82074
+ name: candidate.name,
82075
+ path: found
82076
+ };
82077
+ }
82078
+ }
82079
+ const installs = [];
82080
+ const programFiles = deps.env["ProgramFiles"]?.trim();
82081
+ if (programFiles !== void 0 && programFiles.length > 0) installs.push({
82082
+ name: "pwsh",
82083
+ path: `${programFiles}\\PowerShell\\7\\pwsh.exe`
82084
+ });
82085
+ const systemRoot = deps.env["SystemRoot"]?.trim() ?? deps.env["windir"]?.trim();
82086
+ if (systemRoot !== void 0 && systemRoot.length > 0) installs.push({
82087
+ name: "powershell",
82088
+ path: `${systemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`
82089
+ });
82090
+ for (const candidate of installs) {
82091
+ checked.push(candidate.path);
82092
+ if (await deps.isFile(candidate.path)) return candidate;
82093
+ }
82094
+ }
82095
+ function inferGitBashFromGitExe(gitExe) {
82096
+ const sep = gitExe.includes("\\") ? "\\" : "/";
82097
+ const parts = gitExe.split(sep);
82098
+ for (let i = parts.length - 2; i >= 0; i -= 1) {
82099
+ const segment = parts[i];
82100
+ if (segment === "cmd" || segment === "bin") {
82101
+ const root = parts.slice(0, i).join(sep);
82102
+ return root.length === 0 ? `bin${sep}bash.exe` : `${root}${sep}bin${sep}bash.exe`;
82103
+ }
82104
+ }
82105
+ }
82106
+ function inferBashByWalkingUp(gitExe) {
82107
+ const sep = gitExe.includes("\\") ? "\\" : "/";
82108
+ const parts = gitExe.split(sep);
82109
+ for (let i = parts.length - 1; i >= 1 && parts.length - i <= 6; i -= 1) {
82110
+ const root = parts.slice(0, i).join(sep) || sep;
82111
+ const candidate = `${root}${sep}bin${sep}bash.exe`;
82112
+ const gitDir = parts.slice(0, -1).join(sep);
82113
+ if (`${root}${sep}bin` !== gitDir) return candidate;
82114
+ }
82115
+ }
82116
+ /**
82117
+ * Production convenience — derive the deps bag from Node's ambient surface.
82118
+ *
82119
+ * The result is memoised: subsequent calls return the original promise.
82120
+ * `Environment` is immutable for the lifetime of the process (it derives
82121
+ * from `process.platform`, `process.arch`, `os.release()`, and one-time
82122
+ * shell-path discovery), so caching is sound. Tests that need to probe
82123
+ * with different inputs should call {@link detectEnvironment} directly
82124
+ * with an injected deps bag.
82125
+ */
82126
+ let detectedEnvironment;
82127
+ function detectEnvironmentFromNode() {
82128
+ if (detectedEnvironment !== void 0) return detectedEnvironment;
82129
+ const platform = process.platform;
82130
+ const env = process.env;
82131
+ const isFile = async (path) => {
82132
+ try {
82133
+ await access(path, constants.F_OK);
82134
+ return true;
82135
+ } catch {
82136
+ return false;
82137
+ }
82138
+ };
82139
+ detectedEnvironment = detectEnvironment({
82140
+ platform,
82141
+ arch: process.arch,
82142
+ release: nodeOs.release(),
82143
+ env,
82144
+ isFile,
82145
+ findExecutable: (name) => findExecutableOnPath(name, env["PATH"], platform, isFile)
82146
+ });
82147
+ return detectedEnvironment;
82148
+ }
82149
+ async function findExecutableOnPath(name, pathEnv, platform, isFile) {
82150
+ if (pathEnv === void 0 || pathEnv.length === 0) return void 0;
82151
+ const listSep = platform === "win32" ? ";" : ":";
82152
+ const dirSep = platform === "win32" ? "\\" : "/";
82153
+ for (const rawDir of pathEnv.split(listSep)) {
82154
+ const dir = rawDir.trim();
82155
+ if (dir.length === 0) continue;
82156
+ const candidate = dir.endsWith(dirSep) ? `${dir}${name}` : `${dir}${dirSep}${name}`;
82157
+ if (await isFile(candidate)) return candidate;
82158
+ }
82159
+ }
82160
+ //#endregion
82161
+ //#region ../../packages/jian/src/internal.ts
82162
+ function isUtf8Continuation(byte) {
82163
+ return byte >= 128 && byte <= 191;
82164
+ }
82165
+ function decodeUtf8Ignore(data) {
82166
+ let output = "";
82167
+ let i = 0;
82168
+ while (i < data.length) {
82169
+ const b0 = data[i];
82170
+ if (b0 === void 0) break;
82171
+ if (b0 <= 127) {
82172
+ output += String.fromCodePoint(b0);
82173
+ i += 1;
82174
+ continue;
82175
+ }
82176
+ if (b0 >= 194 && b0 <= 223) {
82177
+ const b1 = data[i + 1];
82178
+ if (b1 !== void 0 && isUtf8Continuation(b1)) {
82179
+ output += String.fromCodePoint((b0 & 31) << 6 | b1 & 63);
82180
+ i += 2;
82181
+ continue;
82182
+ }
82183
+ i += 1;
82184
+ continue;
82185
+ }
82186
+ if (b0 >= 224 && b0 <= 239) {
82187
+ const b1 = data[i + 1];
82188
+ const b2 = data[i + 2];
82189
+ if (b1 !== void 0 && (b0 === 224 && b1 >= 160 && b1 <= 191 || b0 >= 225 && b0 <= 236 && isUtf8Continuation(b1) || b0 === 237 && b1 >= 128 && b1 <= 159 || b0 >= 238 && b0 <= 239 && isUtf8Continuation(b1)) && b2 !== void 0 && isUtf8Continuation(b2)) {
82190
+ output += String.fromCodePoint((b0 & 15) << 12 | (b1 & 63) << 6 | b2 & 63);
82191
+ i += 3;
82192
+ continue;
82193
+ }
82194
+ i += 1;
82195
+ continue;
82196
+ }
82197
+ if (b0 >= 240 && b0 <= 244) {
82198
+ const b1 = data[i + 1];
82199
+ const b2 = data[i + 2];
82200
+ const b3 = data[i + 3];
82201
+ if (b1 !== void 0 && (b0 === 240 && b1 >= 144 && b1 <= 191 || b0 >= 241 && b0 <= 243 && isUtf8Continuation(b1) || b0 === 244 && b1 >= 128 && b1 <= 143) && b2 !== void 0 && b3 !== void 0 && isUtf8Continuation(b2) && isUtf8Continuation(b3)) {
82202
+ output += String.fromCodePoint((b0 & 7) << 18 | (b1 & 63) << 12 | (b2 & 63) << 6 | b3 & 63);
82203
+ i += 4;
82204
+ continue;
82205
+ }
82206
+ i += 1;
82207
+ continue;
82208
+ }
82209
+ i += 1;
82210
+ }
82211
+ return output;
82212
+ }
82213
+ function decodeUtf16LeIgnore(data) {
82214
+ let output = "";
82215
+ let i = 0;
82216
+ while (i + 1 < data.length) {
82217
+ const first = data[i];
82218
+ const second = data[i + 1];
82219
+ if (first === void 0 || second === void 0) break;
82220
+ const codeUnit = first | second << 8;
82221
+ if (codeUnit >= 55296 && codeUnit <= 56319) {
82222
+ const lowFirst = data[i + 2];
82223
+ const lowSecond = data[i + 3];
82224
+ if (lowFirst !== void 0 && lowSecond !== void 0) {
82225
+ const low = lowFirst | lowSecond << 8;
82226
+ if (low >= 56320 && low <= 57343) {
82227
+ const codePoint = 65536 + (codeUnit - 55296 << 10) + (low - 56320);
82228
+ output += String.fromCodePoint(codePoint);
82229
+ i += 4;
82230
+ continue;
82231
+ }
82232
+ }
82233
+ i += 2;
82234
+ continue;
82235
+ }
82236
+ if (codeUnit >= 56320 && codeUnit <= 57343) {
82237
+ i += 2;
82238
+ continue;
82239
+ }
82240
+ output += String.fromCodePoint(codeUnit);
82241
+ i += 2;
82242
+ }
82243
+ return output;
82244
+ }
82245
+ /**
82246
+ * Decode a Buffer into a string with Python-compatible `errors` handling.
82247
+ *
82248
+ * - `'strict'` (default): throw on invalid sequences (via TextDecoder `fatal: true`)
82249
+ * - `'replace'`: substitute each invalid sequence with U+FFFD (TextDecoder default)
82250
+ * - `'ignore'`: drop invalid input sequences while preserving valid U+FFFD characters
82251
+ *
82252
+ * Falls back to `Buffer.toString(encoding)` for encodings TextDecoder does not
82253
+ * support (e.g. `hex`, `base64`, `binary`, `latin1`) — those are lossless
82254
+ * byte-to-character mappings so `errors` has no effect.
82255
+ * @internal
82256
+ */
82257
+ function decodeTextWithErrors(data, encoding, errors = "strict") {
82258
+ let webLabel;
82259
+ switch (encoding) {
82260
+ case "utf-8":
82261
+ case "utf8":
82262
+ webLabel = "utf-8";
82263
+ break;
82264
+ case "utf16le":
82265
+ case "ucs2":
82266
+ case "ucs-2":
82267
+ webLabel = "utf-16le";
82268
+ break;
82269
+ default: webLabel = void 0;
82270
+ }
82271
+ if (webLabel === void 0) return data.toString(encoding);
82272
+ if (errors === "strict") return new TextDecoder(webLabel, { fatal: true }).decode(data);
82273
+ if (errors === "ignore") return webLabel === "utf-8" ? decodeUtf8Ignore(data) : decodeUtf16LeIgnore(data);
82274
+ return new TextDecoder(webLabel, { fatal: false }).decode(data);
82275
+ }
82276
+ /**
82277
+ * Convert a glob pattern segment (e.g. "*.txt", "file?.log") into a RegExp.
82278
+ * Mirrors Python pathlib behavior: includes dotfiles, case-sensitive by default.
82279
+ * @internal
82280
+ */
82281
+ function globPatternToRegex(pattern, caseSensitive) {
82282
+ let regex = "^";
82283
+ for (let i = 0; i < pattern.length; i++) {
82284
+ const ch = pattern[i];
82285
+ if (ch === void 0) break;
82286
+ switch (ch) {
82287
+ case "*":
82288
+ regex += "[^/]*";
82289
+ break;
82290
+ case "?":
82291
+ regex += "[^/]";
82292
+ break;
82293
+ case "[": {
82294
+ const end = pattern.indexOf("]", i + 1);
82295
+ if (end === -1) regex += "\\[";
82296
+ else {
82297
+ let charClass = pattern.slice(i + 1, end);
82298
+ if (charClass.startsWith("!")) charClass = "^" + charClass.slice(1);
82299
+ else if (charClass.startsWith("^")) charClass = "\\" + charClass;
82300
+ regex += "[" + charClass + "]";
82301
+ i = end;
82302
+ }
82303
+ break;
82304
+ }
82305
+ default: regex += ch.replaceAll(/[{}()+.\\^$|]/g, "\\$&");
82306
+ }
82307
+ }
82308
+ regex += "$";
82309
+ return new RegExp(regex, caseSensitive ? "" : "i");
82310
+ }
82311
+ /**
82312
+ * A Readable wrapper that preserves source backpressure while still allowing
82313
+ * consumers to read buffered output after the source has ended.
82314
+ * @internal
82315
+ */
82316
+ var BufferedReadable = class extends Readable {
82317
+ _source;
82318
+ _ended = false;
82319
+ constructor(source) {
82320
+ super({ highWaterMark: 128 * 1024 });
82321
+ this._source = source;
82322
+ this._source.on("data", this._onData);
82323
+ this._source.on("end", this._onEnd);
82324
+ this._source.on("close", this._onClose);
82325
+ this._source.on("error", this._onError);
82326
+ }
82327
+ _read() {
82328
+ if (!this._ended && !this.destroyed) this._source.resume();
82329
+ }
82330
+ _destroy(error, callback) {
82331
+ this._source.off("data", this._onData);
82332
+ this._source.off("end", this._onEnd);
82333
+ this._source.off("close", this._onClose);
82334
+ this._source.off("error", this._onError);
82335
+ callback(error);
82336
+ }
82337
+ _onData = (chunk) => {
82338
+ if (!this.push(chunk)) this._source.pause();
82339
+ };
82340
+ _onEnd = () => {
82341
+ this._ended = true;
82342
+ this.push(null);
82343
+ };
82344
+ _onClose = () => {
82345
+ if (!this._ended) {
82346
+ this._ended = true;
82347
+ this.push(null);
82348
+ }
82349
+ };
82350
+ _onError = (error) => {
82351
+ this.destroy(error);
82352
+ };
82353
+ };
82354
+ //#endregion
82355
+ //#region ../../packages/jian/src/platform.ts
82356
+ /**
82357
+ * Platform dialect — the spawn / kill rules that differ between Windows and
82358
+ * POSIX, written once and shared by every spawn site in this repo.
82359
+ *
82360
+ * Two questions come up at each site, and they must be answered the same way
82361
+ * everywhere or a caller ends up with a half-killed process tree:
82362
+ *
82363
+ * 1. `detached` — on POSIX, `detached: true` makes the child a process-group
82364
+ * leader, which is what lets a single `kill(-pid)` reach the grandchildren
82365
+ * a shell command spawns. Windows has no process groups, so it stays
82366
+ * `false` and relies on `taskkill /T`.
82367
+ * 2. Killing — {@link killProcessTree}, which pairs with (1).
82368
+ *
82369
+ * The platform is always a parameter (never a module-level constant) so tests
82370
+ * can drive both dialects from any host.
82371
+ */
82372
+ /**
82373
+ * True on Windows — the platform with no POSIX process groups, no POSIX
82374
+ * signals, and case-insensitive path comparison.
82375
+ */
82376
+ function isWindowsPlatform(platform) {
82377
+ return platform === "win32";
82378
+ }
82379
+ /**
82380
+ * The `detached` flag for a spawn whose process tree will later be reaped by
82381
+ * {@link killProcessTree}: true exactly when the platform has process groups.
82382
+ */
82383
+ function detachedForProcessTree(platform) {
82384
+ return !isWindowsPlatform(platform);
82385
+ }
82386
+ /**
82387
+ * Kill `pid` and every descendant, resolving once the request has been made.
82388
+ *
82389
+ * Windows: `taskkill /F /T /PID <pid>`. `/T` walks the tree; `/F` is
82390
+ * unconditional because without it `taskkill` only posts `WM_CLOSE`, which
82391
+ * console processes — i.e. everything an agent runs — ignore, so a "graceful"
82392
+ * Windows kill would report success while leaving the tree running. Exit code
82393
+ * 128 ("process not found") resolves, mirroring the POSIX `ESRCH` branch below:
82394
+ * an already-dead tree is the state the caller asked for.
82395
+ *
82396
+ * POSIX: signal the process group, which requires the child to have been
82397
+ * spawned with `detached: true` (see {@link detachedForProcessTree}).
82398
+ * `ESRCH` resolves for the same reason. Anything else rejects so the caller can
82399
+ * decide whether to surface it.
82400
+ */
82401
+ async function killProcessTree(pid, options) {
82402
+ if (pid <= 0) return;
82403
+ if (isWindowsPlatform(options.platform ?? process.platform)) {
82404
+ await runTaskkill(pid);
82405
+ return;
82406
+ }
82407
+ try {
82408
+ process.kill(-pid, options.signal);
82409
+ } catch (error) {
82410
+ if (error.code === "ESRCH") return;
82411
+ throw error;
82412
+ }
82413
+ }
82414
+ const TASKKILL_PROCESS_NOT_FOUND = 128;
82415
+ function runTaskkill(pid) {
82416
+ return new Promise((resolve, reject) => {
82417
+ const killer = spawn("taskkill", [
82418
+ "/F",
82419
+ "/T",
82420
+ "/PID",
82421
+ String(pid)
82422
+ ], {
82423
+ stdio: "ignore",
82424
+ windowsHide: true
82425
+ });
82426
+ killer.once("error", (error) => {
82427
+ reject(/* @__PURE__ */ new Error(`taskkill failed to start: ${error.message}`));
82428
+ });
82429
+ killer.once("close", (code) => {
82430
+ if (code === 0 || code === TASKKILL_PROCESS_NOT_FOUND) resolve();
82431
+ else reject(/* @__PURE__ */ new Error(`taskkill exited with code ${String(code)}`));
82432
+ });
82433
+ killer.unref();
82434
+ });
82435
+ }
82436
+ //#endregion
82437
+ //#region ../../packages/jian/src/local.ts
82438
+ /**
82439
+ * Environment variables that spawned processes are allowed to inherit from the
82440
+ * parent process. All other variables are stripped to prevent accidental secret
82441
+ * leakage (e.g. cloud tokens, API keys, SSH agent sockets) into agent-executed
82442
+ * commands. Explicit env values passed by callers can still add or override keys.
82443
+ */
82444
+ const ALLOWED_INHERITED_ENV_KEYS = [
82445
+ "PATH",
82446
+ "PATHEXT",
82447
+ "SHELL",
82448
+ "ComSpec",
82449
+ "HOME",
82450
+ "USER",
82451
+ "USERNAME",
82452
+ "LOGNAME",
82453
+ "USERPROFILE",
82454
+ "HOMEDRIVE",
82455
+ "HOMEPATH",
82456
+ "LANG",
82457
+ "LC_ALL",
82458
+ "LC_CTYPE",
82459
+ "LC_MESSAGES",
82460
+ "TERM",
82461
+ "TERM_PROGRAM",
82462
+ "TERM_PROGRAM_VERSION",
82463
+ "COLORTERM",
82464
+ "NO_COLOR",
82465
+ "FORCE_COLOR",
82466
+ "TMPDIR",
82467
+ "TEMP",
82468
+ "TMP",
82469
+ "APPDATA",
82470
+ "LOCALAPPDATA",
82471
+ "XDG_CONFIG_HOME",
82472
+ "XDG_CACHE_HOME",
82473
+ "XDG_DATA_HOME",
82474
+ "XDG_RUNTIME_DIR",
82475
+ "GIT_TERMINAL_PROMPT",
82476
+ "GIT_ASKPASS",
82477
+ "SSH_ASKPASS",
82478
+ "SCREAM_PID"
82479
+ ];
82480
+ /**
82481
+ * True if `candidate` is `base` itself or a descendant of `base`, compared on
82482
+ * path-component boundaries. Both paths must already be normalized. This is a
82483
+ * lexical check only; it does not resolve symlinks.
82484
+ *
82485
+ * `platform` decides whether the comparison folds case: Windows paths are
82486
+ * case-insensitive, POSIX paths are not.
82487
+ */
82488
+ function isWithinDirectory(candidate, base, platform) {
82489
+ const normalizedCandidate = normalize(candidate);
82490
+ const normalizedBase = normalize(base);
82491
+ const windows = isWindowsPlatform(platform);
82492
+ const comparableCandidate = windows ? normalizedCandidate.toLowerCase() : normalizedCandidate;
82493
+ const comparableBase = windows ? normalizedBase.toLowerCase() : normalizedBase;
82494
+ if (comparableCandidate === comparableBase) return true;
82495
+ const prefix = comparableBase.endsWith("/") ? comparableBase : `${comparableBase}/`;
82496
+ return comparableCandidate.startsWith(prefix);
82497
+ }
82498
+ /**
82499
+ * Build a sanitized environment for child processes. Inherits only an explicit
82500
+ * allowlist of ambient variables, then applies caller-supplied overrides.
82501
+ */
82502
+ function buildSafeEnv(explicit) {
82503
+ const env = {};
82504
+ for (const key of ALLOWED_INHERITED_ENV_KEYS) {
82505
+ const value = process.env[key];
82506
+ if (value !== void 0) env[key] = value;
82507
+ }
82508
+ if (explicit !== void 0) for (const [key, value] of Object.entries(explicit)) env[key] = value;
82509
+ return env;
82510
+ }
82511
+ /**
82512
+ * Build the `(dev, ino)` cycle-detection key used by `_globWalk`'s
82513
+ * visited set. Returns `null` when `ino` is 0, which Node returns on
82514
+ * filesystems that don't carry inodes (Windows FAT/exFAT, some SMB/NFS
82515
+ * mounts). A null key signals "no reliable identity for this dir" so
82516
+ * the caller skips visited tracking for that descent — cycle safety
82517
+ * is weakened on those filesystems, but normal walking works instead
82518
+ * of every directory colliding on the shared key `"<dev>:0"`.
82519
+ */
82520
+ function cycleKey(s) {
82521
+ if (s.ino === 0) return null;
82522
+ return `${String(s.dev)}:${String(s.ino)}`;
82523
+ }
82524
+ const DEFAULT_PATHEXT = [
82525
+ ".COM",
82526
+ ".EXE",
82527
+ ".BAT",
82528
+ ".CMD"
82529
+ ];
82530
+ /** Extensions Windows can only run through `cmd.exe`, never by direct exec. */
82531
+ const CMD_SCRIPT_EXTENSIONS = [".bat", ".cmd"];
82532
+ /**
82533
+ * Windows spawn plan for `command`.
82534
+ *
82535
+ * Windows does not resolve a bare name to a runnable file the way POSIX does: a
82536
+ * shell walks `PATH` + `PATHEXT` and lands on `npm.cmd`, while Node's `spawn`
82537
+ * does not, which is why `spawn('npm')` fails on a host where `npm.cmd` works
82538
+ * in every terminal. This reproduces that resolution and then launches the
82539
+ * result the way the OS requires:
82540
+ *
82541
+ * - anything runnable as-is (`.exe` / `.com` / extensionless) → spawned directly.
82542
+ * - `.cmd` / `.bat` → through `cmd.exe /d /s /c`. A batch file is a script for
82543
+ * `cmd`, so there is no direct-exec route — Node refuses to spawn one
82544
+ * anyway (it rejects `.bat`/`.cmd` without `shell: true`). Handing a fully
82545
+ * quoted command line to `cmd.exe` keeps argument parsing under our
82546
+ * control, which is exactly why `shell: true` is not used: that would let
82547
+ * Node re-tokenize the whole line for us, including the parts we build.
82548
+ *
82549
+ * The command line is built by {@link quoteCmdToken}, so a command or argument
82550
+ * that cannot be quoted for a batch file (an embedded `"` or line break)
82551
+ * rejects the call instead of being passed through — see there for why no
82552
+ * escaping exists.
82553
+ *
82554
+ * @internal for tests
82555
+ */
82556
+ async function windowsSpawnPlan(command, args, env) {
82557
+ const resolved = await resolveWindowsExecutable(command, env["PATH"], pathextExtensions(env["PATHEXT"]));
82558
+ if (!isCmdScript(resolved)) return {
82559
+ command: resolved,
82560
+ args,
82561
+ windowsVerbatimArguments: false
82562
+ };
82563
+ return {
82564
+ command: env["ComSpec"] ?? env["COMSPEC"] ?? "cmd.exe",
82565
+ args: [
82566
+ "/d",
82567
+ "/s",
82568
+ "/c",
82569
+ buildCmdCommandLine(resolved, args)
82570
+ ],
82571
+ windowsVerbatimArguments: true
82572
+ };
82573
+ }
82574
+ /** `PATHEXT` entries verbatim (Windows compares extensions case-insensitively); its own default when unset/empty. */
82575
+ function pathextExtensions(pathext) {
82576
+ const entries = (pathext ?? "").split(";").map((extension) => extension.trim()).filter((extension) => extension.length > 0);
82577
+ return entries.length > 0 ? entries : DEFAULT_PATHEXT;
82578
+ }
82579
+ function isCmdScript(path) {
82580
+ const lower = path.toLowerCase();
82581
+ return CMD_SCRIPT_EXTENSIONS.some((extension) => lower.endsWith(extension));
82582
+ }
82583
+ /**
82584
+ * Find the runnable file `command` names, trying the literal name first and then
82585
+ * every `PATHEXT` suffix in order (the order a shell uses). A name containing a
82586
+ * separator is a path rather than a `PATH` lookup, but Windows still appends
82587
+ * `PATHEXT` candidates to it, so the candidate list is the same.
82588
+ *
82589
+ * Throws a readable `JianExecError` naming the command and every name that was
82590
+ * tried, because the bare `ENOENT` a raw `spawn` produces names neither the
82591
+ * suffixes nor the search roots.
82592
+ */
82593
+ async function resolveWindowsExecutable(command, pathEnv, extensions) {
82594
+ const isPath = /[\\/]/.test(command);
82595
+ const searchDirs = isPath ? [""] : pathDirectories(pathEnv);
82596
+ const names = [command, ...extensions.map((extension) => `${command}${extension}`)];
82597
+ for (const dir of searchDirs) for (const name of names) {
82598
+ const candidate = join(dir, name);
82599
+ if (await fileExists(candidate)) return candidate;
82600
+ }
82601
+ throw new JianExecError(`Command not found: ${command}. Searched ${isPath ? "the path given in the command" : `PATH (${pathEnv ?? ""})`} for ${names.join(", ")}.`, command, "ENOENT");
82602
+ }
82603
+ function pathDirectories(pathEnv) {
82604
+ return (pathEnv ?? "").split(";").map((dir) => dir.trim()).filter((dir) => dir.length > 0);
82605
+ }
82606
+ async function fileExists(path) {
82607
+ try {
82608
+ return (await stat(path)).isFile();
82609
+ } catch {
82610
+ return false;
82611
+ }
82612
+ }
82613
+ /**
82614
+ * Quote one token for a `cmd.exe /s /c` command line.
82615
+ *
82616
+ * Batch files are interpreted by `cmd`, so anything reaching a `.cmd` shim is
82617
+ * subject to cmd metacharacters. Wrapping a token that carries whitespace or a
82618
+ * metacharacter in double quotes keeps `&` / `|` / `<` / `>` from starting a
82619
+ * second command — which is the entire job of this helper, because `shell: true`
82620
+ * is deliberately not used and nothing else would quote these tokens.
82621
+ *
82622
+ * A token containing `"` is refused, not escaped. `\` is not an escape
82623
+ * character in `cmd`; `"` *toggles* the quoting state, so a `\"` closes the
82624
+ * quoted region early and leaves the rest of the token outside it — an `&` that
82625
+ * follows then reaches `cmd` as a real command separator and the remainder runs
82626
+ * as a second command at the caller's privilege. There is no quoted form of a
82627
+ * `"` that survives a batch shim, so the only safe answer is to fail closed.
82628
+ * CR/LF are refused for the same reason: they end the line `cmd.exe /c` parses,
82629
+ * turning the rest into further commands.
82630
+ *
82631
+ * `%VAR%` is expanded by `cmd` *inside* the quotes. That is inherent to invoking
82632
+ * a batch file and cannot be escaped away; not masking it is the honest form,
82633
+ * and the alternative would be refusing to run `npm`, `pnpm` and every other
82634
+ * shim shipped as a `.cmd`.
82635
+ *
82636
+ * An empty token is quoted too: it is still one argument slot, and collapsing it
82637
+ * to nothing would shift every argument that follows (`install -g ''` would
82638
+ * become `install -g`).
82639
+ */
82640
+ function quoteCmdToken(token) {
82641
+ if (/["\r\n]/.test(token)) throw new JianExecError(`Cannot quote ${JSON.stringify(token)} for a batch file: cmd.exe has no escape character — a double quote toggles its quoting state instead of being escaped, so the rest of the token would fall outside the quotes and split into further commands. A double quote or a line break cannot be passed through a .cmd/.bat argument list.`, token);
82642
+ if (token === "") return "\"\"";
82643
+ return /[\s&|<>^()]/.test(token) ? `"${token}"` : token;
82644
+ }
82645
+ /**
82646
+ * The `cmd.exe /s /c` command line for a batch shim: `cmd.exe /s` strips exactly
82647
+ * one outer quote pair, then takes the rest verbatim, so the whole line is
82648
+ * wrapped and every token inside it is quoted by {@link quoteCmdToken}.
82649
+ *
82650
+ * Exported because a caller outside this package has to build the same line for
82651
+ * the same reason — the CLI's `npm` launch plan spawns `npm.cmd` through
82652
+ * `cmd.exe` on Windows, and a second copy of the quoting rules is exactly the
82653
+ * kind of drift that lets one call site become injectable while the other is
82654
+ * fixed. It throws a `JianExecError` for a token no batch file can carry.
82655
+ */
82656
+ function buildCmdCommandLine(command, args) {
82657
+ return `"${[command, ...args].map(quoteCmdToken).join(" ")}"`;
82658
+ }
82659
+ var LocalProcess = class {
82660
+ stdin;
82661
+ stdout;
82662
+ stderr;
82663
+ pid;
82664
+ _child;
82665
+ _platform;
82666
+ _exitCode = null;
82667
+ _exitPromise;
82668
+ constructor(child, platform) {
82669
+ if (child.stdin === null || child.stdout === null || child.stderr === null) throw new Error("Process must be created with stdin/stdout/stderr pipes.");
82670
+ this._child = child;
82671
+ this._platform = platform;
82672
+ this.stdin = child.stdin;
82673
+ this.stdout = new BufferedReadable(child.stdout);
82674
+ this.stderr = new BufferedReadable(child.stderr);
82675
+ this.pid = child.pid ?? -1;
82676
+ this._exitPromise = new Promise((resolve, reject) => {
82677
+ child.on("exit", (code) => {
82678
+ this._exitCode = code ?? -1;
82679
+ resolve(this._exitCode);
82680
+ });
82681
+ child.on("error", (error) => {
82682
+ reject(error);
82683
+ });
82684
+ });
82685
+ }
82686
+ get exitCode() {
82687
+ return this._exitCode;
82688
+ }
82689
+ async wait() {
82690
+ return this._exitPromise;
82691
+ }
82692
+ async kill(signal) {
82693
+ const deliver = signal ?? "SIGTERM";
82694
+ try {
82695
+ await killProcessTree(this.pid, {
82696
+ signal: deliver,
82697
+ platform: this._platform
82698
+ });
82699
+ } catch (error) {
82700
+ if (error.code !== "EPERM") throw error;
82701
+ try {
82702
+ this._child.kill(deliver);
82703
+ } catch {}
82704
+ }
82705
+ }
82706
+ };
82707
+ let atomicWriteCounter = 0;
82708
+ /**
82709
+ * A JIAN implementation that directly interacts with the local filesystem.
82710
+ *
82711
+ * Note: LocalJian maintains its own per-instance working directory (`_cwd`)
82712
+ * rather than mutating `process.cwd()`. This lets multiple LocalJian instances
82713
+ * coexist with independent cwds (e.g. when switching contexts via
82714
+ * `runWithJian`) without cross-polluting each other's relative-path resolution.
82715
+ */
82716
+ var LocalJian = class LocalJian {
82717
+ name = "local";
82718
+ osEnv;
82719
+ _cwd;
82720
+ _rootDir;
82721
+ /**
82722
+ * Spawn / kill / path-case dialect for this instance, derived from the probed
82723
+ * environment: `osKind` is `'Windows'` exactly when the probe saw win32.
82724
+ * Injected rather than read from `process.platform` so tests can drive the
82725
+ * Windows dialect from any host — same shape as `environment.ts`, which takes
82726
+ * an injected platform probe.
82727
+ */
82728
+ _platform;
82729
+ constructor(osEnv, cwd, rootDir) {
82730
+ this._cwd = normalize(cwd ?? process.cwd());
82731
+ this._rootDir = rootDir === void 0 ? void 0 : normalize(rootDir);
82732
+ this._platform = osEnv.osKind === "Windows" ? "win32" : "posix";
82733
+ this.osEnv = osEnv;
82734
+ }
82735
+ /**
82736
+ * Construct a fresh `LocalJian` after probing the host environment.
82737
+ *
82738
+ * Each call returns a new instance with its own `_cwd`; concurrent
82739
+ * callers can therefore operate on independent working directories
82740
+ * without polluting one another.
82741
+ */
82742
+ static async create(cwd, rootDir, env) {
82743
+ return new LocalJian(env ?? await detectEnvironmentFromNode(), cwd, rootDir);
82744
+ }
82745
+ withCwd(cwd) {
82746
+ return new LocalJian(this.osEnv, cwd, this._rootDir);
82747
+ }
82748
+ _resolvePath(path) {
82749
+ const resolved = isAbsolute$1(path) ? normalize(path) : join$1(this._cwd, path);
82750
+ this._assertWithinRoot(resolved);
82751
+ return resolved;
82752
+ }
82753
+ _assertWithinRoot(resolvedPath) {
82754
+ if (this._rootDir === void 0) return;
82755
+ if (isWithinDirectory(resolvedPath, this._rootDir, this._platform)) return;
82756
+ throw new JianPathOutsideRootError(`Path outside allowed root directory: ${resolvedPath}`, resolvedPath, this._rootDir);
82757
+ }
82758
+ /** Resolve path for sandboxed operations — lexical check + realpath. */
82759
+ async _resolveSandboxedPath(path) {
82760
+ const lexical = isAbsolute$1(path) ? normalize(path) : join$1(this._cwd, path);
82761
+ if (!isWithinDirectory(lexical, this._rootDir, this._platform)) throw new JianPathOutsideRootError(`Path outside allowed root directory: ${lexical}`, lexical, this._rootDir);
82762
+ const realPath = await realpath(lexical);
82763
+ if (!isWithinDirectory(realPath, await realpath(this._rootDir), this._platform)) throw new JianPathOutsideRootError(`Path outside allowed root directory (via symlink): ${lexical}`, lexical, this._rootDir);
82764
+ return realPath;
82765
+ }
82766
+ pathClass() {
82767
+ return this._platform;
82768
+ }
82769
+ normpath(path) {
82770
+ return normalize(path);
82771
+ }
82772
+ gethome() {
82773
+ return normalize(homedir());
82774
+ }
82775
+ getcwd() {
82776
+ return this._cwd;
82777
+ }
82778
+ /**
82779
+ * Change the working directory of this LocalJian instance.
82780
+ *
82781
+ * Unlike Python's `os.chdir`, this is instance-scoped and never touches
82782
+ * `process.cwd()`. Child processes spawned via {@link exec} inherit this
82783
+ * instance's `_cwd`; concurrent LocalJian instances each carry their own
82784
+ * independent cwd. If you need Python-compatible process-global cwd,
82785
+ * call `process.chdir(x)` directly.
82786
+ */
82787
+ async chdir(path) {
82788
+ const resolved = this._rootDir !== void 0 ? await this._resolveSandboxedPath(path) : this._resolvePath(path);
82789
+ if (!(await stat(resolved)).isDirectory()) throw new Error(`Not a directory: ${resolved}`);
82790
+ this._cwd = resolved;
82791
+ }
82792
+ async realpath(path, options) {
82793
+ const lexical = this._resolvePath(path);
82794
+ try {
82795
+ return normalize(await realpath(lexical));
82796
+ } catch (error) {
82797
+ const code = error.code;
82798
+ if (!options?.allowMissing || code !== "ENOENT" && code !== "ENOTDIR") throw error;
82799
+ }
82800
+ const missingSegments = [];
82801
+ let ancestor = lexical;
82802
+ while (true) {
82803
+ try {
82804
+ await lstat(ancestor);
82805
+ } catch (error) {
82806
+ const code = error.code;
82807
+ if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
82808
+ const parent = dirname$2(ancestor);
82809
+ if (parent === ancestor) throw error;
82810
+ missingSegments.push(basename$1(ancestor));
82811
+ ancestor = parent;
82812
+ continue;
82813
+ }
82814
+ return normalize(join$1(normalize(await realpath(ancestor)), ...missingSegments.toReversed()));
82815
+ }
82816
+ }
82817
+ async stat(path, options) {
82818
+ const followSymlinks = options?.followSymlinks ?? true;
82819
+ const resolved = this._rootDir !== void 0 && followSymlinks ? await this._resolveSandboxedPath(path) : this._resolvePath(path);
82820
+ const s = followSymlinks ? await stat(resolved) : await lstat(resolved);
82821
+ return {
82822
+ stMode: s.mode,
82823
+ stIno: s.ino,
82824
+ stDev: s.dev,
82825
+ stNlink: s.nlink,
82826
+ stUid: s.uid,
82827
+ stGid: s.gid,
82828
+ stSize: s.size,
82829
+ stAtime: s.atimeMs / 1e3,
82830
+ stMtime: s.mtimeMs / 1e3,
82831
+ stCtime: isWindowsPlatform(this._platform) ? s.birthtimeMs / 1e3 : s.ctimeMs / 1e3
82832
+ };
82833
+ }
82834
+ async *iterdir(path) {
82835
+ const resolved = this._resolvePath(path);
82836
+ const entries = await readdir(resolved);
82837
+ for (const entry of entries) yield join$1(resolved, entry);
82838
+ }
82839
+ async *glob(path, pattern, options) {
82840
+ const resolved = this._resolvePath(path);
82841
+ const caseSensitive = options?.caseSensitive ?? true;
82842
+ const physicalAllowedRoots = options?.allowedRoots === void 0 ? void 0 : await Promise.all(options.allowedRoots.map((root) => this.realpath(root, { allowMissing: true })));
82843
+ if (!await this._isWithinPhysicalRoots(resolved, physicalAllowedRoots)) return;
82844
+ const patternParts = pattern.split("/");
82845
+ const initVisited = /* @__PURE__ */ new Set();
82846
+ try {
82847
+ const rootKey = cycleKey(await stat(resolved));
82848
+ if (rootKey !== null) initVisited.add(rootKey);
82849
+ } catch {}
82850
+ yield* this._globWalk(resolved, patternParts, caseSensitive, initVisited, physicalAllowedRoots);
82851
+ }
82852
+ async *_globWalk(basePath, patternParts, caseSensitive, visited, physicalAllowedRoots) {
82853
+ if (!await this._isWithinPhysicalRoots(basePath, physicalAllowedRoots)) return;
82854
+ if (patternParts.length === 0) return;
82855
+ const [currentPattern, ...remainingParts] = patternParts;
82856
+ if (currentPattern === "**") {
82857
+ if (remainingParts.length > 0) yield* this._globWalk(basePath, remainingParts, caseSensitive, visited, physicalAllowedRoots);
82858
+ else yield basePath;
82859
+ let entries;
82860
+ try {
82861
+ entries = await readdir(basePath);
82862
+ } catch {
82863
+ return;
82864
+ }
82865
+ for (const entry of entries) {
82866
+ const fullPath = join$1(basePath, entry);
82867
+ if (this._rootDir && !isWithinDirectory(fullPath, this._rootDir, this._platform)) continue;
82868
+ let entryStat;
82869
+ try {
82870
+ entryStat = await stat(fullPath);
82871
+ } catch {
82872
+ continue;
82873
+ }
82874
+ if (entryStat.isDirectory()) {
82875
+ const key = cycleKey(entryStat);
82876
+ if (key !== null && visited.has(key)) continue;
82877
+ yield* this._globWalk(fullPath, patternParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited, physicalAllowedRoots);
82878
+ } else if (remainingParts.length === 0 && await this._isWithinPhysicalRoots(fullPath, physicalAllowedRoots)) yield fullPath;
82879
+ }
82880
+ } else {
82881
+ const regex = globPatternToRegex(currentPattern ?? "", caseSensitive);
82882
+ let entries;
82883
+ try {
82884
+ entries = await readdir(basePath);
82885
+ } catch {
82886
+ return;
82887
+ }
82888
+ for (const entry of entries) {
82889
+ if (!regex.test(entry)) continue;
82890
+ const fullPath = join$1(basePath, entry);
82891
+ if (this._rootDir && !isWithinDirectory(fullPath, this._rootDir, this._platform)) continue;
82892
+ if (remainingParts.length === 0) {
82893
+ if (await this._isWithinPhysicalRoots(fullPath, physicalAllowedRoots)) yield fullPath;
82894
+ } else {
82895
+ let entryStat;
82896
+ try {
82897
+ entryStat = await stat(fullPath);
82898
+ } catch {
82899
+ continue;
82900
+ }
82901
+ if (entryStat.isDirectory()) {
82902
+ const key = cycleKey(entryStat);
82903
+ if (key !== null && visited.has(key)) continue;
82904
+ yield* this._globWalk(fullPath, remainingParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited, physicalAllowedRoots);
82905
+ }
82906
+ }
82907
+ }
82908
+ }
82909
+ }
82910
+ async _isWithinPhysicalRoots(path, physicalAllowedRoots) {
82911
+ if (physicalAllowedRoots === void 0) return true;
82912
+ try {
82913
+ const physicalPath = normalize(await realpath(path));
82914
+ return physicalAllowedRoots.some((root) => isWithinDirectory(physicalPath, root, this._platform));
82915
+ } catch {
82916
+ return false;
82917
+ }
82918
+ }
82919
+ async readBytes(path, n) {
82920
+ const resolved = this._resolvePath(path);
82921
+ if (n === void 0) return Buffer.from(await readFile(resolved));
82922
+ const fh = await open(resolved, "r");
82923
+ try {
82924
+ const buf = Buffer.alloc(n);
82925
+ const { bytesRead } = await fh.read(buf, 0, n, 0);
82926
+ return buf.subarray(0, bytesRead);
82927
+ } finally {
82928
+ await fh.close();
82929
+ }
82930
+ }
82931
+ async readText(path, options) {
82932
+ const resolved = this._resolvePath(path);
82933
+ const encoding = options?.encoding ?? "utf-8";
82934
+ const errors = options?.errors ?? "strict";
82935
+ return decodeTextWithErrors(await readFile(resolved), encoding, errors);
82936
+ }
82937
+ async *readLines(path, options) {
82938
+ const stream = createReadStream(this._resolvePath(path), {
82939
+ encoding: options?.encoding ?? "utf-8",
82940
+ highWaterMark: 64 * 1024
82941
+ });
82942
+ let remainder = "";
82943
+ try {
82944
+ for await (const chunk of stream) {
82945
+ const lines = (remainder + chunk).split("\n");
82946
+ remainder = lines.pop() ?? "";
82947
+ for (const line of lines) yield line + "\n";
82948
+ }
82949
+ } finally {
82950
+ stream.destroy();
82951
+ }
82952
+ if (remainder !== "") yield remainder;
82953
+ }
82954
+ async writeBytes(path, data) {
82955
+ await writeFile(this._resolvePath(path), data);
82956
+ return data.length;
82957
+ }
82958
+ async writeText(path, data, options) {
82959
+ const resolved = this._resolvePath(path);
82960
+ const encoding = options?.encoding ?? "utf-8";
82961
+ if ((options?.mode ?? "w") === "a") await appendFile(resolved, data, encoding);
82962
+ else await writeFile(resolved, data, encoding);
82963
+ return data.length;
82964
+ }
82965
+ async writeTextAtomic(path, data, options) {
82966
+ const resolved = this._resolvePath(path);
82967
+ const encoding = options?.encoding ?? "utf-8";
82968
+ const tmpPath = `${resolved}.${process.pid}.${Date.now().toString(36)}.${(atomicWriteCounter++).toString(36)}.tmp`;
82969
+ await writeFile(tmpPath, data, encoding);
82970
+ await rename(tmpPath, resolved);
82971
+ return data.length;
82972
+ }
82973
+ async mkdir(path, options) {
82974
+ const resolved = this._resolvePath(path);
82975
+ const parents = options?.parents ?? false;
82976
+ const existOk = options?.existOk ?? false;
82977
+ if (parents) {
82978
+ if (!existOk) try {
82979
+ if ((await stat(resolved)).isDirectory()) throw new JianFileExistsError(`${resolved} already exists`);
82980
+ } catch (error) {
82981
+ if (error instanceof JianFileExistsError) throw error;
82982
+ if (error.code !== "ENOENT") throw error;
82983
+ }
82984
+ await mkdir(resolved, { recursive: true });
82985
+ return;
82986
+ }
82987
+ try {
82988
+ await mkdir(resolved);
82989
+ } catch (error) {
82990
+ if (existOk && error instanceof Error && "code" in error && error.code === "EEXIST") {
82991
+ if (!(await stat(resolved)).isDirectory()) throw new JianFileExistsError(`${resolved} already exists but is not a directory`);
82992
+ return;
82993
+ }
82994
+ throw error;
82995
+ }
82996
+ }
82997
+ async exec(...args) {
82998
+ return this.execWithEnv(args, void 0);
82999
+ }
83000
+ async execWithEnv(args, env) {
83001
+ const command = args[0];
83002
+ if (command === void 0) throw new Error("LocalJian.execWithEnv(): at least one argument (the command to run) is required.");
83003
+ const childEnv = buildSafeEnv(env);
83004
+ const plan = isWindowsPlatform(this._platform) ? await windowsSpawnPlan(command, args.slice(1), childEnv) : {
83005
+ command,
83006
+ args: args.slice(1),
83007
+ windowsVerbatimArguments: false
83008
+ };
83009
+ const child = spawn(plan.command, plan.args, {
83010
+ cwd: this._cwd,
83011
+ stdio: [
83012
+ "pipe",
83013
+ "pipe",
83014
+ "pipe"
83015
+ ],
83016
+ detached: detachedForProcessTree(this._platform),
83017
+ env: childEnv,
83018
+ windowsVerbatimArguments: plan.windowsVerbatimArguments,
83019
+ windowsHide: true
83020
+ });
83021
+ try {
83022
+ await waitForSpawn(child);
83023
+ } catch (error) {
83024
+ if (error instanceof Error) throw new JianExecError(`Failed to spawn ${plan.command}: ${error.message}`, plan.command, error.code);
83025
+ throw error;
83026
+ }
83027
+ return new LocalProcess(child, this._platform);
83028
+ }
83029
+ };
83030
+ function waitForSpawn(child) {
83031
+ return new Promise((resolve, reject) => {
83032
+ const onSpawn = () => {
83033
+ child.off("error", onError);
83034
+ resolve();
83035
+ };
83036
+ const onError = (err) => {
83037
+ child.off("spawn", onSpawn);
83038
+ reject(err);
83039
+ };
83040
+ child.once("spawn", onSpawn);
83041
+ child.once("error", onError);
83042
+ });
83043
+ }
83044
+ new AsyncLocalStorage();
83045
+ //#endregion
81505
83046
  //#region ../../packages/agent-core/src/session/hooks/runner.ts
81506
83047
  const DEFAULT_TIMEOUT_SECONDS = 30;
81507
83048
  const KILL_GRACE_MS = 100;
@@ -81526,7 +83067,7 @@ async function runHook(command, input, options) {
81526
83067
  shell: true,
81527
83068
  cwd: options.cwd,
81528
83069
  stdio: "pipe",
81529
- detached: process.platform !== "win32"
83070
+ detached: detachedForProcessTree(process.platform)
81530
83071
  });
81531
83072
  } catch (error) {
81532
83073
  return allowResult({ stderr: errorMessage$2(error) });
@@ -81654,21 +83195,41 @@ function allowResult(input) {
81654
83195
  structuredOutput: input.structuredOutput
81655
83196
  };
81656
83197
  }
83198
+ /**
83199
+ * Tear down the hook's whole process tree, not just the shell it was spawned as.
83200
+ * A hook command that starts children of its own (`sh -c 'foo & bar'`) would
83201
+ * otherwise keep running past the timeout — and past the session that started
83202
+ * it.
83203
+ *
83204
+ * POSIX keeps the two-phase signal (SIGTERM, then SIGKILL after a short grace)
83205
+ * because the shell leads its own process group. Windows has no POSIX signals:
83206
+ * `killProcessTree` force-kills the tree in one step, since a "graceful" phase
83207
+ * there would only post `WM_CLOSE`, which console processes ignore — it would
83208
+ * report success while the tree kept running.
83209
+ */
81657
83210
  function killProcess(child) {
81658
- tryKillProcess(child, "SIGTERM");
81659
- setTimeout(() => {
81660
- tryKillProcess(child, "SIGKILL");
81661
- }, KILL_GRACE_MS).unref();
81662
- }
81663
- function tryKillProcess(child, signal) {
81664
- try {
81665
- if (process.platform !== "win32" && child.pid !== void 0) process.kill(-child.pid, signal);
81666
- else child.kill(signal);
81667
- } catch {
83211
+ const pid = child.pid;
83212
+ if (pid === void 0) {
81668
83213
  try {
81669
- child.kill(signal);
83214
+ child.kill();
81670
83215
  } catch {}
83216
+ return;
81671
83217
  }
83218
+ const killTree = (signal) => {
83219
+ killProcessTree(pid, { signal }).catch(() => {
83220
+ try {
83221
+ child.kill(signal);
83222
+ } catch {}
83223
+ });
83224
+ };
83225
+ if (isWindowsPlatform(process.platform)) {
83226
+ killTree("SIGKILL");
83227
+ return;
83228
+ }
83229
+ killTree("SIGTERM");
83230
+ setTimeout(() => {
83231
+ killTree("SIGKILL");
83232
+ }, KILL_GRACE_MS).unref();
81672
83233
  }
81673
83234
  function isRecord$2(value) {
81674
83235
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -82185,6 +83746,16 @@ var PlanModeInjector = class extends DynamicInjector {
82185
83746
  if (strategy === "fusion") return variant === "full" ? fusionFullReminder(planFilePath) : variant === "sparse" ? fusionSparseReminder(planFilePath) : fusionReentryReminder(planFilePath);
82186
83747
  return variant === "full" ? fullReminder(planFilePath) : variant === "sparse" ? sparseReminder(planFilePath) : reentryReminder(planFilePath);
82187
83748
  }
83749
+ /**
83750
+ * Picks the reminder variant for the current injection point.
83751
+ *
83752
+ * A user-authored message resets the cadence to the full reminder. The test
83753
+ * is `isUserAuthoredMessage`, never the role: system reminders, injected
83754
+ * context, scheduled notifications and hook results all land in history as
83755
+ * user-role messages (`ContextMemory.appendSystemReminder`), so matching on
83756
+ * the role alone would let any injection on any step re-trigger the full
83757
+ * reminder and make `PLAN_MODE_FULL_REFRESH_TURNS` unreachable.
83758
+ */
82188
83759
  getVariant() {
82189
83760
  if (this.injectedAt === null) return "full";
82190
83761
  const history = this.agent.context.history;
@@ -82196,7 +83767,7 @@ var PlanModeInjector = class extends DynamicInjector {
82196
83767
  assistantTurnsSince += 1;
82197
83768
  continue;
82198
83769
  }
82199
- if (msg.role === "user") return "full";
83770
+ if (isUserAuthoredMessage(msg)) return "full";
82200
83771
  }
82201
83772
  if (assistantTurnsSince >= PLAN_MODE_FULL_REFRESH_TURNS) return "full";
82202
83773
  if (assistantTurnsSince >= PLAN_MODE_DEDUP_MIN_TURNS) return "sparse";
@@ -82325,24 +83896,28 @@ Do NOT write the plan manually. Use the FusionPlan tool to generate it, or Edit
82325
83896
  const TODO_LIST_TOOL_NAME = "TodoList";
82326
83897
  const TODO_STORE_KEY = "todo";
82327
83898
  const TODO_LIST_REMINDER_VARIANT = "todo_list_reminder";
82328
- const TODO_LIST_REMINDER_TURNS_SINCE_WRITE = 10;
82329
- const TODO_LIST_REMINDER_TURNS_BETWEEN_REMINDERS = 10;
83899
+ const TODO_LIST_REMINDER_TURNS_SINCE_WRITE = 20;
83900
+ const TODO_LIST_REMINDER_TURNS_BETWEEN_REMINDERS = 20;
82330
83901
  /**
82331
83902
  * Periodically reminds the model to update its todo list when it hasn't
82332
83903
  * been touched for many turns. Prevents stale, forgotten task lists.
82333
83904
  *
82334
83905
  * Fires when:
82335
83906
  * - TodoList tool is active
82336
- * - >= 10 turns since the last todo write
82337
- * - >= 10 turns since the last reminder
83907
+ * - the list is non-empty and still has at least one unfinished item
83908
+ * (nothing to maintain means nothing to say)
83909
+ * - >= 20 turns since the last todo write
83910
+ * - >= 20 turns since the last reminder
82338
83911
  */
82339
83912
  var TodoListReminderInjector = class extends DynamicInjector {
82340
83913
  injectionVariant = TODO_LIST_REMINDER_VARIANT;
82341
83914
  getInjection() {
82342
83915
  if (!this.isTodoListActive()) return void 0;
83916
+ const todos = this.currentTodos();
83917
+ if (todos.every((todo) => todo.status === "done")) return void 0;
82343
83918
  const counts = getTodoListReminderTurnCounts(this.agent.context.history);
82344
83919
  if (counts.turnsSinceLastWrite < TODO_LIST_REMINDER_TURNS_SINCE_WRITE || counts.turnsSinceLastReminder < TODO_LIST_REMINDER_TURNS_BETWEEN_REMINDERS) return;
82345
- return renderTodoListReminder(this.currentTodos());
83920
+ return renderTodoListReminder(todos);
82346
83921
  }
82347
83922
  isTodoListActive() {
82348
83923
  return this.agent.tools.data().some((tool) => {
@@ -82410,7 +83985,7 @@ function isTodoItem(value) {
82410
83985
  return typeof record["title"] === "string" && isTodoStatus(record["status"]);
82411
83986
  }
82412
83987
  function isTodoStatus(value) {
82413
- return value === "pending" || value === "in_progress" || value === "done";
83988
+ return value === "pending" || value === "in_progress" || value === "done" || value === "blocked";
82414
83989
  }
82415
83990
  //#endregion
82416
83991
  //#region ../../packages/agent-core/src/profile/context.ts
@@ -82872,11 +84447,8 @@ var BotModePermissionPolicy = class BotModePermissionPolicy {
82872
84447
  isAllowedWritePath(rawPath) {
82873
84448
  if (rawPath.trim().length === 0) return false;
82874
84449
  if (rawPath.startsWith("~")) return false;
82875
- const cwd = this.agent.config?.cwd ?? ".";
82876
- const norm = normalize(rawPath.startsWith("/") ? rawPath : join(cwd, rawPath));
82877
- const normCwd = normalize(cwd);
82878
- if (norm !== normCwd && !norm.startsWith(normCwd.endsWith(sep) ? normCwd : normCwd + sep)) return false;
82879
- if (BotModePermissionPolicy.SENSITIVE_WRITE.test(basename(norm))) return false;
84450
+ if (!isPathInside(this.agent.config?.cwd ?? ".", rawPath)) return false;
84451
+ if (BotModePermissionPolicy.SENSITIVE_WRITE.test(basename$1(normalize(rawPath)))) return false;
82880
84452
  return true;
82881
84453
  }
82882
84454
  evaluate(context) {
@@ -83060,7 +84632,7 @@ const S_IFDIR = 16384;
83060
84632
  const S_IFREG = 32768;
83061
84633
  async function findGitWorkTreeMarker(jian, cwd) {
83062
84634
  if (cwd.length === 0 || !isAbsolute$1(cwd)) return null;
83063
- let current = normalize$1(cwd);
84635
+ let current = normalize(cwd);
83064
84636
  for (let depth = 0; depth < 256; depth += 1) {
83065
84637
  const hit = await probeGitMarker(jian, join$1(current, ".git"), current);
83066
84638
  if (hit !== null) return hit;
@@ -83108,7 +84680,7 @@ function parseGitDir(content, markerParent) {
83108
84680
  if (line === void 0 || !line.startsWith("gitdir:")) return void 0;
83109
84681
  const rawPath = line.slice(7).trim();
83110
84682
  if (rawPath.length === 0) return void 0;
83111
- return normalize$1(isAbsolute$1(rawPath) ? rawPath : join$1(markerParent, rawPath));
84683
+ return normalize(isAbsolute$1(rawPath) ? rawPath : join$1(markerParent, rawPath));
83112
84684
  }
83113
84685
  //#endregion
83114
84686
  //#region ../../packages/agent-core/src/agent/permission/policies/file-access-ask.ts
@@ -85899,20 +87471,6 @@ var AgentRecords = class {
85899
87471
  await this.persistence?.flush();
85900
87472
  }
85901
87473
  };
85902
- /** Mirrors the TUI's isReplayUserTurnRecord predicate (message-replay.ts):
85903
- * a replay record starts a user turn when it is a user-role message from the
85904
- * user (or a user-slash skill activation). */
85905
- function isUserTurnStartRecord(record) {
85906
- if (record.type !== "message") return false;
85907
- const { message } = record;
85908
- if (message.role !== "user") return false;
85909
- switch (message.origin?.kind) {
85910
- case void 0:
85911
- case "user": return true;
85912
- case "skill_activation": return message.origin.trigger === "user-slash";
85913
- default: return false;
85914
- }
85915
- }
85916
87474
  var ReplayBuilder = class {
85917
87475
  agent;
85918
87476
  records = [];
@@ -85926,7 +87484,7 @@ var ReplayBuilder = class {
85926
87484
  push(record) {
85927
87485
  if (!this.agent.records.restoring) return;
85928
87486
  this.records.push(record);
85929
- if (!isUserTurnStartRecord(record)) return;
87487
+ if (record.type !== "message" || !isRealUserPrompt(record.message)) return;
85930
87488
  this.userTurnStarts.push(this.records.length - 1);
85931
87489
  if (this.userTurnStarts.length <= 10) return;
85932
87490
  const dropBefore = this.userTurnStarts[this.userTurnStarts.length - 10];
@@ -94837,8 +96395,14 @@ async function startCallbackServer() {
94837
96395
  function mcpCredentialsDir(screamHomeDir) {
94838
96396
  return join$1(screamHomeDir, "credentials", "mcp");
94839
96397
  }
96398
+ /**
96399
+ * Fallback location for the credential store, used when the caller did not
96400
+ * pass a `screamHomeDir`. Resolved through the same home resolver as every
96401
+ * other caller, so `SCREAM_CODE_HOME` moves this store too instead of leaving
96402
+ * it under the built-in `~/.scream-code` default.
96403
+ */
94840
96404
  function defaultMcpCredentialsDir() {
94841
- return mcpCredentialsDir(join$1(homedir(), ".scream-code"));
96405
+ return mcpCredentialsDir(resolveScreamHome());
94842
96406
  }
94843
96407
  function sanitizeStoreKey(name) {
94844
96408
  const safe = basename$1(name).replaceAll(/[^a-zA-Z0-9_-]/g, "_").replaceAll(/_+/g, "_");
@@ -98810,7 +100374,7 @@ function readRequiredSource(sources, path) {
98810
100374
  return content;
98811
100375
  }
98812
100376
  function normalizeSourcePath(path) {
98813
- return normalize$1(path.replaceAll("\\", "/")).replace(/^\.\//, "");
100377
+ return normalize(path.replaceAll("\\", "/")).replace(/^\.\//, "");
98814
100378
  }
98815
100379
  //#endregion
98816
100380
  //#region ../../packages/agent-core/src/profile/default/agent.yaml
@@ -100461,9 +102025,14 @@ var ToolCallDeduplicator = class {
100461
102025
  const TURN_DEFAULTS = {
100462
102026
  /**
100463
102027
  * Convergence gate: max times the turn injects a `<system-reminder>` to
100464
- * force continuation (empty step, missing TodoList for an active goal,
100465
- * non-exploratory tool failure, failed verification). Bounded so a model
100466
- * that can't converge ends the turn instead of looping forever.
102028
+ * force continuation (empty step, non-exploratory tool failure, failed
102029
+ * verification) — all `correction`-category reasons in the stop gate's
102030
+ * three-category scheme (`correction` / `external-input` / `quality-floor`;
102031
+ * see 「续轮输出纪律」 in `docs/turn-pipeline.md`). Bounded so a model that
102032
+ * can't converge ends the turn instead of looping forever. Internal state
102033
+ * upkeep (checklist bookkeeping, goal progress, summary polish) is never a
102034
+ * reason: those reminders run before the answer, never as a post-answer
102035
+ * continuation.
100467
102036
  */
100468
102037
  maxConvergenceInjections: 3,
100469
102038
  /**
@@ -100915,10 +102484,19 @@ var TurnFlow = class {
100915
102484
  kind: "system_trigger",
100916
102485
  name: "todo_required"
100917
102486
  });
100918
- if (stepNumber === 2 && !this.todoSeenThisTurn) this.agent.context.appendSystemReminder("This task spans multiple steps. Use TodoList to track the remaining work and current phase.", {
102487
+ if (stepNumber === 3 && !this.todoSeenThisTurn) this.agent.context.appendSystemReminder("This task spans multiple steps. Use TodoList to track the remaining work and current phase.", {
100919
102488
  kind: "system_trigger",
100920
102489
  name: "todo_suggested"
100921
102490
  });
102491
+ if (!this.todoReconcileInjected && this.todoSeenThisTurn) {
102492
+ if (this.agent.tools.getTodos().some((todo) => todo.status !== "done")) {
102493
+ this.todoReconcileInjected = true;
102494
+ this.agent.context.appendSystemReminder("Before you write your final answer this turn, reconcile the TodoList with reality: mark only work that is actually finished as done, leave unfinished items pending or in_progress, and keep blockers accurate. Do not mark incomplete work as done. Fold this into the work you are already doing rather than spending a separate round on it.", {
102495
+ kind: "system_trigger",
102496
+ name: "todo_reconcile"
102497
+ });
102498
+ }
102499
+ }
100922
102500
  if (stepNumber === 1 || this.agent.fullCompaction.shouldInjectSessionSummary()) {
100923
102501
  const sessionSummary = this.agent.sessionMemory.getSessionSummary();
100924
102502
  if (sessionSummary.length > 0) this.agent.context.appendSystemReminder(sessionSummary, {
@@ -100955,7 +102533,6 @@ var TurnFlow = class {
100955
102533
  if (this.convergenceInjections < TURN_DEFAULTS.maxConvergenceInjections) {
100956
102534
  const reasons = [];
100957
102535
  if (!this.currentStepHadContent) reasons.push("The last assistant step produced no content or tool calls. Continue the task.");
100958
- if (this.agent.goal.getGoal().goal?.status === "active" && !this.todoSeenThisTurn) reasons.push("An active goal exists but no TodoList update was made this turn. Update TodoList and continue.");
100959
102536
  if (this.lastToolFailure?.isExploratory === false && !hasPassedVerificationThisTurn) reasons.push(`A required tool (${this.lastToolFailure.toolName}) failed this turn. Analyze the error and fix it before reporting completion.`);
100960
102537
  if (latestVerification && !latestVerification.passed && !this.verificationFailureInjected) {
100961
102538
  this.verificationFailureInjected = true;
@@ -100997,17 +102574,6 @@ var TurnFlow = class {
100997
102574
  }
100998
102575
  }
100999
102576
  if (this.flushSteerBuffer()) return { continue: true };
101000
- if (!this.todoReconcileInjected && stopReason !== "max_tokens" && this.agent.background.list().length === 0) {
101001
- const todos = this.agent.tools.getTodos();
101002
- if (todos.some((todo) => todo.status !== "done") && todos.length > 0) {
101003
- this.todoReconcileInjected = true;
101004
- this.agent.context.appendSystemReminder("Before ending the turn, reconcile the TodoList with reality: mark only work that is actually finished as done, leave unfinished items pending or in_progress, and keep blockers accurate. Do not mark incomplete work as done. Prefer updating TodoList only on this continuation.", {
101005
- kind: "system_trigger",
101006
- name: "todo_reconcile"
101007
- });
101008
- return { continue: true };
101009
- }
101010
- }
101011
102577
  return { continue: false };
101012
102578
  },
101013
102579
  prepareToolExecution: async (ctx) => {
@@ -101055,7 +102621,7 @@ var TurnFlow = class {
101055
102621
  }
101056
102622
  }
101057
102623
  }
101058
- if (ctx.toolCall.name === "TodoList") this.todoSeenThisTurn = true;
102624
+ if (ctx.toolCall.name === "TodoList" && isError !== true) this.todoSeenThisTurn = true;
101059
102625
  if (isError === true && [
101060
102626
  "Edit",
101061
102627
  "Write",
@@ -101653,7 +103219,7 @@ var Agent = class {
101653
103219
  }) : Promise.resolve();
101654
103220
  this.sessionMemory = new SessionMemory(this);
101655
103221
  this.workingSet = new WorkingSet();
101656
- this.dreamTracker = new DreamTracker(screamHomeDir ?? "");
103222
+ this.dreamTracker = new DreamTracker(resolveScreamHome(screamHomeDir));
101657
103223
  this.replayBuilder = new ReplayBuilder(this);
101658
103224
  this.services = {
101659
103225
  records: this.records,
@@ -123141,7 +124707,7 @@ async function readSessionIndex(homeDir, sessionsDir) {
123141
124707
  const sessionDir = resolve$2(entry.sessionDir);
123142
124708
  if (!isAbsolute$1(entry.sessionDir)) continue;
123143
124709
  if (!isAbsolute$1(entry.workDir)) continue;
123144
- if (!isPathInside(sessionsDir, sessionDir)) continue;
124710
+ if (sessionDir === sessionsDir || !isPathInside(sessionsDir, sessionDir)) continue;
123145
124711
  if (basename$1(sessionDir) !== entry.sessionId) continue;
123146
124712
  result.set(entry.sessionId, {
123147
124713
  sessionId: entry.sessionId,
@@ -123193,10 +124759,6 @@ function parseIndexLine(line) {
123193
124759
  return;
123194
124760
  }
123195
124761
  }
123196
- function isPathInside(parent, child) {
123197
- const rel = relative$1(resolve$2(parent), resolve$2(child));
123198
- return rel !== "" && !rel.startsWith("..") && !isAbsolute$1(rel);
123199
- }
123200
124762
  //#endregion
123201
124763
  //#region ../../packages/agent-core/src/utils/workdir-slug.ts
123202
124764
  const MAX_WORKDIR_SLUG_LENGTH = 40;
@@ -123565,903 +125127,6 @@ function compareSessionSummary(a, b) {
123565
125127
  return 0;
123566
125128
  }
123567
125129
  //#endregion
123568
- //#region ../../packages/jian/src/errors.ts
123569
- /**
123570
- * Base error class for the jian package.
123571
- */
123572
- var JianError = class extends Error {
123573
- constructor(message) {
123574
- super(message);
123575
- this.name = "JianError";
123576
- }
123577
- };
123578
- /**
123579
- * Equivalent to Python's FileExistsError — indicates a file or directory already exists.
123580
- */
123581
- var JianFileExistsError = class extends JianError {
123582
- constructor(message) {
123583
- super(message);
123584
- this.name = "JianFileExistsError";
123585
- }
123586
- };
123587
- /**
123588
- * Thrown by `detectEnvironment` on Windows when no Git Bash install can be
123589
- * located. Carries the list of paths that were probed so callers can include
123590
- * them in install hints.
123591
- */
123592
- var JianShellNotFoundError = class extends JianError {
123593
- constructor(message) {
123594
- super(message);
123595
- this.name = "JianShellNotFoundError";
123596
- }
123597
- };
123598
- /**
123599
- * Thrown by `LocalJian` when a file operation would resolve outside the
123600
- * instance's configured root directory. This is a last-line-of-defense guard;
123601
- * higher-level path policies should still validate user-supplied paths first.
123602
- */
123603
- var JianPathOutsideRootError = class extends JianError {
123604
- path;
123605
- rootDir;
123606
- constructor(message, path, rootDir) {
123607
- super(message);
123608
- this.path = path;
123609
- this.rootDir = rootDir;
123610
- this.name = "JianPathOutsideRootError";
123611
- }
123612
- };
123613
- /** Thrown when process execution fails (spawn error, ENOENT, EACCES). */
123614
- var JianExecError = class extends JianError {
123615
- command;
123616
- code;
123617
- constructor(message, command, code) {
123618
- super(message);
123619
- this.command = command;
123620
- this.code = code;
123621
- this.name = "JianExecError";
123622
- }
123623
- };
123624
- //#endregion
123625
- //#region ../../packages/jian/src/environment.ts
123626
- /**
123627
- * Environment — cross-platform probe of OS / shell.
123628
- *
123629
- * Detection is a pure function of injected probes (`platform` / `arch` /
123630
- * `release` / `env` / `isFile` / `findExecutable`) so the same suite runs
123631
- * identically on any host OS. `detectEnvironmentFromNode()` bundles the
123632
- * Node defaults for production callers.
123633
- *
123634
- * On Windows the probe expects Git Bash (the canonical POSIX shell that
123635
- * ships with Git for Windows). If it cannot be located the function
123636
- * throws `JianShellNotFoundError`; the SDK layer can wrap that into a
123637
- * user-facing install hint. Set `SCREAM_SHELL_PATH` to override.
123638
- */
123639
- function resolveOsKind(platform) {
123640
- switch (platform) {
123641
- case "darwin": return "macOS";
123642
- case "linux": return "Linux";
123643
- case "win32": return "Windows";
123644
- default: return platform;
123645
- }
123646
- }
123647
- async function detectEnvironment(deps) {
123648
- const osKind = resolveOsKind(deps.platform);
123649
- const osArch = deps.arch;
123650
- const osVersion = deps.release;
123651
- if (deps.platform === "win32") return {
123652
- osKind,
123653
- osArch,
123654
- osVersion,
123655
- shellName: "bash",
123656
- shellPath: await locateWindowsGitBash(deps)
123657
- };
123658
- const candidates = [
123659
- "/bin/bash",
123660
- "/usr/bin/bash",
123661
- "/usr/local/bin/bash"
123662
- ];
123663
- let found;
123664
- for (const p of candidates) if (await deps.isFile(p)) {
123665
- found = p;
123666
- break;
123667
- }
123668
- if (found !== void 0) return {
123669
- osKind,
123670
- osArch,
123671
- osVersion,
123672
- shellName: "bash",
123673
- shellPath: found
123674
- };
123675
- return {
123676
- osKind,
123677
- osArch,
123678
- osVersion,
123679
- shellName: "sh",
123680
- shellPath: "/bin/sh"
123681
- };
123682
- }
123683
- async function locateWindowsGitBash(deps) {
123684
- const checked = [];
123685
- const override = deps.env["SCREAM_SHELL_PATH"]?.trim();
123686
- if (override !== void 0 && override.length > 0) {
123687
- checked.push(override);
123688
- if (await deps.isFile(override)) return override;
123689
- }
123690
- const gitExe = await deps.findExecutable("git.exe");
123691
- if (gitExe !== void 0) {
123692
- const inferred = inferGitBashFromGitExe(gitExe);
123693
- if (inferred !== void 0) {
123694
- checked.push(inferred);
123695
- if (await deps.isFile(inferred)) return inferred;
123696
- }
123697
- const fellback = inferBashByWalkingUp(gitExe);
123698
- if (fellback !== void 0) {
123699
- checked.push(fellback);
123700
- if (await deps.isFile(fellback)) return fellback;
123701
- }
123702
- }
123703
- const bashExe = await deps.findExecutable("bash.exe");
123704
- if (bashExe !== void 0) {
123705
- checked.push(bashExe);
123706
- return bashExe;
123707
- }
123708
- const candidates = [
123709
- "C:\\Program Files\\Git\\bin\\bash.exe",
123710
- "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
123711
- "C:\\msys64\\usr\\bin\\bash.exe",
123712
- "C:\\cygwin64\\bin\\bash.exe"
123713
- ];
123714
- const localAppData = deps.env["LOCALAPPDATA"]?.trim();
123715
- if (localAppData !== void 0 && localAppData.length > 0) candidates.push(`${localAppData}\\Programs\\Git\\bin\\bash.exe`);
123716
- const userProfile = deps.env["USERPROFILE"]?.trim();
123717
- if (userProfile !== void 0 && userProfile.length > 0) candidates.push(`${userProfile}\\scoop\\apps\\git\\current\\bin\\bash.exe`);
123718
- for (const candidate of candidates) {
123719
- checked.push(candidate);
123720
- if (await deps.isFile(candidate)) return candidate;
123721
- }
123722
- throw new JianShellNotFoundError(`Git Bash was not found on this Windows host. Install Git for Windows from https://gitforwindows.org/ or set SCREAM_SHELL_PATH to a bash.exe. Checked: ${checked.join(", ")}.`);
123723
- }
123724
- function inferGitBashFromGitExe(gitExe) {
123725
- const sep = gitExe.includes("\\") ? "\\" : "/";
123726
- const parts = gitExe.split(sep);
123727
- for (let i = parts.length - 2; i >= 0; i -= 1) {
123728
- const segment = parts[i];
123729
- if (segment === "cmd" || segment === "bin") {
123730
- const root = parts.slice(0, i).join(sep);
123731
- return root.length === 0 ? `bin${sep}bash.exe` : `${root}${sep}bin${sep}bash.exe`;
123732
- }
123733
- }
123734
- }
123735
- function inferBashByWalkingUp(gitExe) {
123736
- const sep = gitExe.includes("\\") ? "\\" : "/";
123737
- const parts = gitExe.split(sep);
123738
- for (let i = parts.length - 1; i >= 1 && parts.length - i <= 6; i -= 1) {
123739
- const root = parts.slice(0, i).join(sep) || sep;
123740
- const candidate = `${root}${sep}bin${sep}bash.exe`;
123741
- const gitDir = parts.slice(0, -1).join(sep);
123742
- if (`${root}${sep}bin` !== gitDir) return candidate;
123743
- }
123744
- }
123745
- /**
123746
- * Production convenience — derive the deps bag from Node's ambient surface.
123747
- *
123748
- * The result is memoised: subsequent calls return the original promise.
123749
- * `Environment` is immutable for the lifetime of the process (it derives
123750
- * from `process.platform`, `process.arch`, `os.release()`, and one-time
123751
- * shell-path discovery), so caching is sound. Tests that need to probe
123752
- * with different inputs should call {@link detectEnvironment} directly
123753
- * with an injected deps bag.
123754
- */
123755
- let detectedEnvironment;
123756
- function detectEnvironmentFromNode() {
123757
- if (detectedEnvironment !== void 0) return detectedEnvironment;
123758
- const platform = process.platform;
123759
- const env = process.env;
123760
- const isFile = async (path) => {
123761
- try {
123762
- await access(path, constants.F_OK);
123763
- return true;
123764
- } catch {
123765
- return false;
123766
- }
123767
- };
123768
- detectedEnvironment = detectEnvironment({
123769
- platform,
123770
- arch: process.arch,
123771
- release: nodeOs.release(),
123772
- env,
123773
- isFile,
123774
- findExecutable: (name) => findExecutableOnPath(name, env["PATH"], platform, isFile)
123775
- });
123776
- return detectedEnvironment;
123777
- }
123778
- async function findExecutableOnPath(name, pathEnv, platform, isFile) {
123779
- if (pathEnv === void 0 || pathEnv.length === 0) return void 0;
123780
- const listSep = platform === "win32" ? ";" : ":";
123781
- const dirSep = platform === "win32" ? "\\" : "/";
123782
- for (const rawDir of pathEnv.split(listSep)) {
123783
- const dir = rawDir.trim();
123784
- if (dir.length === 0) continue;
123785
- const candidate = dir.endsWith(dirSep) ? `${dir}${name}` : `${dir}${dirSep}${name}`;
123786
- if (await isFile(candidate)) return candidate;
123787
- }
123788
- }
123789
- //#endregion
123790
- //#region ../../packages/jian/src/internal.ts
123791
- function isUtf8Continuation(byte) {
123792
- return byte >= 128 && byte <= 191;
123793
- }
123794
- function decodeUtf8Ignore(data) {
123795
- let output = "";
123796
- let i = 0;
123797
- while (i < data.length) {
123798
- const b0 = data[i];
123799
- if (b0 === void 0) break;
123800
- if (b0 <= 127) {
123801
- output += String.fromCodePoint(b0);
123802
- i += 1;
123803
- continue;
123804
- }
123805
- if (b0 >= 194 && b0 <= 223) {
123806
- const b1 = data[i + 1];
123807
- if (b1 !== void 0 && isUtf8Continuation(b1)) {
123808
- output += String.fromCodePoint((b0 & 31) << 6 | b1 & 63);
123809
- i += 2;
123810
- continue;
123811
- }
123812
- i += 1;
123813
- continue;
123814
- }
123815
- if (b0 >= 224 && b0 <= 239) {
123816
- const b1 = data[i + 1];
123817
- const b2 = data[i + 2];
123818
- if (b1 !== void 0 && (b0 === 224 && b1 >= 160 && b1 <= 191 || b0 >= 225 && b0 <= 236 && isUtf8Continuation(b1) || b0 === 237 && b1 >= 128 && b1 <= 159 || b0 >= 238 && b0 <= 239 && isUtf8Continuation(b1)) && b2 !== void 0 && isUtf8Continuation(b2)) {
123819
- output += String.fromCodePoint((b0 & 15) << 12 | (b1 & 63) << 6 | b2 & 63);
123820
- i += 3;
123821
- continue;
123822
- }
123823
- i += 1;
123824
- continue;
123825
- }
123826
- if (b0 >= 240 && b0 <= 244) {
123827
- const b1 = data[i + 1];
123828
- const b2 = data[i + 2];
123829
- const b3 = data[i + 3];
123830
- if (b1 !== void 0 && (b0 === 240 && b1 >= 144 && b1 <= 191 || b0 >= 241 && b0 <= 243 && isUtf8Continuation(b1) || b0 === 244 && b1 >= 128 && b1 <= 143) && b2 !== void 0 && b3 !== void 0 && isUtf8Continuation(b2) && isUtf8Continuation(b3)) {
123831
- output += String.fromCodePoint((b0 & 7) << 18 | (b1 & 63) << 12 | (b2 & 63) << 6 | b3 & 63);
123832
- i += 4;
123833
- continue;
123834
- }
123835
- i += 1;
123836
- continue;
123837
- }
123838
- i += 1;
123839
- }
123840
- return output;
123841
- }
123842
- function decodeUtf16LeIgnore(data) {
123843
- let output = "";
123844
- let i = 0;
123845
- while (i + 1 < data.length) {
123846
- const first = data[i];
123847
- const second = data[i + 1];
123848
- if (first === void 0 || second === void 0) break;
123849
- const codeUnit = first | second << 8;
123850
- if (codeUnit >= 55296 && codeUnit <= 56319) {
123851
- const lowFirst = data[i + 2];
123852
- const lowSecond = data[i + 3];
123853
- if (lowFirst !== void 0 && lowSecond !== void 0) {
123854
- const low = lowFirst | lowSecond << 8;
123855
- if (low >= 56320 && low <= 57343) {
123856
- const codePoint = 65536 + (codeUnit - 55296 << 10) + (low - 56320);
123857
- output += String.fromCodePoint(codePoint);
123858
- i += 4;
123859
- continue;
123860
- }
123861
- }
123862
- i += 2;
123863
- continue;
123864
- }
123865
- if (codeUnit >= 56320 && codeUnit <= 57343) {
123866
- i += 2;
123867
- continue;
123868
- }
123869
- output += String.fromCodePoint(codeUnit);
123870
- i += 2;
123871
- }
123872
- return output;
123873
- }
123874
- /**
123875
- * Decode a Buffer into a string with Python-compatible `errors` handling.
123876
- *
123877
- * - `'strict'` (default): throw on invalid sequences (via TextDecoder `fatal: true`)
123878
- * - `'replace'`: substitute each invalid sequence with U+FFFD (TextDecoder default)
123879
- * - `'ignore'`: drop invalid input sequences while preserving valid U+FFFD characters
123880
- *
123881
- * Falls back to `Buffer.toString(encoding)` for encodings TextDecoder does not
123882
- * support (e.g. `hex`, `base64`, `binary`, `latin1`) — those are lossless
123883
- * byte-to-character mappings so `errors` has no effect.
123884
- * @internal
123885
- */
123886
- function decodeTextWithErrors(data, encoding, errors = "strict") {
123887
- let webLabel;
123888
- switch (encoding) {
123889
- case "utf-8":
123890
- case "utf8":
123891
- webLabel = "utf-8";
123892
- break;
123893
- case "utf16le":
123894
- case "ucs2":
123895
- case "ucs-2":
123896
- webLabel = "utf-16le";
123897
- break;
123898
- default: webLabel = void 0;
123899
- }
123900
- if (webLabel === void 0) return data.toString(encoding);
123901
- if (errors === "strict") return new TextDecoder(webLabel, { fatal: true }).decode(data);
123902
- if (errors === "ignore") return webLabel === "utf-8" ? decodeUtf8Ignore(data) : decodeUtf16LeIgnore(data);
123903
- return new TextDecoder(webLabel, { fatal: false }).decode(data);
123904
- }
123905
- /**
123906
- * Convert a glob pattern segment (e.g. "*.txt", "file?.log") into a RegExp.
123907
- * Mirrors Python pathlib behavior: includes dotfiles, case-sensitive by default.
123908
- * @internal
123909
- */
123910
- function globPatternToRegex(pattern, caseSensitive) {
123911
- let regex = "^";
123912
- for (let i = 0; i < pattern.length; i++) {
123913
- const ch = pattern[i];
123914
- if (ch === void 0) break;
123915
- switch (ch) {
123916
- case "*":
123917
- regex += "[^/]*";
123918
- break;
123919
- case "?":
123920
- regex += "[^/]";
123921
- break;
123922
- case "[": {
123923
- const end = pattern.indexOf("]", i + 1);
123924
- if (end === -1) regex += "\\[";
123925
- else {
123926
- let charClass = pattern.slice(i + 1, end);
123927
- if (charClass.startsWith("!")) charClass = "^" + charClass.slice(1);
123928
- else if (charClass.startsWith("^")) charClass = "\\" + charClass;
123929
- regex += "[" + charClass + "]";
123930
- i = end;
123931
- }
123932
- break;
123933
- }
123934
- default: regex += ch.replaceAll(/[{}()+.\\^$|]/g, "\\$&");
123935
- }
123936
- }
123937
- regex += "$";
123938
- return new RegExp(regex, caseSensitive ? "" : "i");
123939
- }
123940
- /**
123941
- * A Readable wrapper that preserves source backpressure while still allowing
123942
- * consumers to read buffered output after the source has ended.
123943
- * @internal
123944
- */
123945
- var BufferedReadable = class extends Readable {
123946
- _source;
123947
- _ended = false;
123948
- constructor(source) {
123949
- super({ highWaterMark: 128 * 1024 });
123950
- this._source = source;
123951
- this._source.on("data", this._onData);
123952
- this._source.on("end", this._onEnd);
123953
- this._source.on("close", this._onClose);
123954
- this._source.on("error", this._onError);
123955
- }
123956
- _read() {
123957
- if (!this._ended && !this.destroyed) this._source.resume();
123958
- }
123959
- _destroy(error, callback) {
123960
- this._source.off("data", this._onData);
123961
- this._source.off("end", this._onEnd);
123962
- this._source.off("close", this._onClose);
123963
- this._source.off("error", this._onError);
123964
- callback(error);
123965
- }
123966
- _onData = (chunk) => {
123967
- if (!this.push(chunk)) this._source.pause();
123968
- };
123969
- _onEnd = () => {
123970
- this._ended = true;
123971
- this.push(null);
123972
- };
123973
- _onClose = () => {
123974
- if (!this._ended) {
123975
- this._ended = true;
123976
- this.push(null);
123977
- }
123978
- };
123979
- _onError = (error) => {
123980
- this.destroy(error);
123981
- };
123982
- };
123983
- //#endregion
123984
- //#region ../../packages/jian/src/local.ts
123985
- /**
123986
- * Environment variables that spawned processes are allowed to inherit from the
123987
- * parent process. All other variables are stripped to prevent accidental secret
123988
- * leakage (e.g. cloud tokens, API keys, SSH agent sockets) into agent-executed
123989
- * commands. Explicit env values passed by callers can still add or override keys.
123990
- */
123991
- const ALLOWED_INHERITED_ENV_KEYS = [
123992
- "PATH",
123993
- "PATHEXT",
123994
- "SHELL",
123995
- "ComSpec",
123996
- "HOME",
123997
- "USER",
123998
- "USERNAME",
123999
- "LOGNAME",
124000
- "USERPROFILE",
124001
- "HOMEDRIVE",
124002
- "HOMEPATH",
124003
- "LANG",
124004
- "LC_ALL",
124005
- "LC_CTYPE",
124006
- "LC_MESSAGES",
124007
- "TERM",
124008
- "TERM_PROGRAM",
124009
- "TERM_PROGRAM_VERSION",
124010
- "COLORTERM",
124011
- "NO_COLOR",
124012
- "FORCE_COLOR",
124013
- "TMPDIR",
124014
- "TEMP",
124015
- "TMP",
124016
- "APPDATA",
124017
- "LOCALAPPDATA",
124018
- "XDG_CONFIG_HOME",
124019
- "XDG_CACHE_HOME",
124020
- "XDG_DATA_HOME",
124021
- "XDG_RUNTIME_DIR",
124022
- "GIT_TERMINAL_PROMPT",
124023
- "GIT_ASKPASS",
124024
- "SSH_ASKPASS",
124025
- "SCREAM_PID"
124026
- ];
124027
- const isWindows = process.platform === "win32";
124028
- /**
124029
- * True if `candidate` is `base` itself or a descendant of `base`, compared on
124030
- * path-component boundaries. Both paths must already be normalized. This is a
124031
- * lexical check only; it does not resolve symlinks.
124032
- */
124033
- function isWithinDirectory(candidate, base) {
124034
- const normalizedCandidate = normalize$1(candidate);
124035
- const normalizedBase = normalize$1(base);
124036
- const comparableCandidate = isWindows ? normalizedCandidate.toLowerCase() : normalizedCandidate;
124037
- const comparableBase = isWindows ? normalizedBase.toLowerCase() : normalizedBase;
124038
- if (comparableCandidate === comparableBase) return true;
124039
- const prefix = comparableBase.endsWith("/") ? comparableBase : `${comparableBase}/`;
124040
- return comparableCandidate.startsWith(prefix);
124041
- }
124042
- /**
124043
- * Build a sanitized environment for child processes. Inherits only an explicit
124044
- * allowlist of ambient variables, then applies caller-supplied overrides.
124045
- */
124046
- function buildSafeEnv(explicit) {
124047
- const env = {};
124048
- for (const key of ALLOWED_INHERITED_ENV_KEYS) {
124049
- const value = process.env[key];
124050
- if (value !== void 0) env[key] = value;
124051
- }
124052
- if (explicit !== void 0) for (const [key, value] of Object.entries(explicit)) env[key] = value;
124053
- return env;
124054
- }
124055
- /**
124056
- * Build the `(dev, ino)` cycle-detection key used by `_globWalk`'s
124057
- * visited set. Returns `null` when `ino` is 0, which Node returns on
124058
- * filesystems that don't carry inodes (Windows FAT/exFAT, some SMB/NFS
124059
- * mounts). A null key signals "no reliable identity for this dir" so
124060
- * the caller skips visited tracking for that descent — cycle safety
124061
- * is weakened on those filesystems, but normal walking works instead
124062
- * of every directory colliding on the shared key `"<dev>:0"`.
124063
- */
124064
- function cycleKey(s) {
124065
- if (s.ino === 0) return null;
124066
- return `${String(s.dev)}:${String(s.ino)}`;
124067
- }
124068
- var LocalProcess = class {
124069
- stdin;
124070
- stdout;
124071
- stderr;
124072
- pid;
124073
- _child;
124074
- _exitCode = null;
124075
- _exitPromise;
124076
- constructor(child) {
124077
- if (child.stdin === null || child.stdout === null || child.stderr === null) throw new Error("Process must be created with stdin/stdout/stderr pipes.");
124078
- this._child = child;
124079
- this.stdin = child.stdin;
124080
- this.stdout = new BufferedReadable(child.stdout);
124081
- this.stderr = new BufferedReadable(child.stderr);
124082
- this.pid = child.pid ?? -1;
124083
- this._exitPromise = new Promise((resolve, reject) => {
124084
- child.on("exit", (code) => {
124085
- this._exitCode = code ?? -1;
124086
- resolve(this._exitCode);
124087
- });
124088
- child.on("error", (error) => {
124089
- reject(error);
124090
- });
124091
- });
124092
- }
124093
- get exitCode() {
124094
- return this._exitCode;
124095
- }
124096
- async wait() {
124097
- return this._exitPromise;
124098
- }
124099
- kill(signal) {
124100
- if (this.pid <= 0) return Promise.resolve();
124101
- if (isWindows) {
124102
- const taskkillArgs = signal === "SIGKILL" ? [
124103
- "/T",
124104
- "/F",
124105
- "/PID",
124106
- String(this.pid)
124107
- ] : [
124108
- "/T",
124109
- "/PID",
124110
- String(this.pid)
124111
- ];
124112
- return new Promise((resolve, reject) => {
124113
- const killer = spawn("taskkill", taskkillArgs, {
124114
- stdio: "ignore",
124115
- windowsHide: true
124116
- });
124117
- killer.once("error", (err) => {
124118
- reject(/* @__PURE__ */ new Error(`taskkill failed to start: ${err.message}`));
124119
- });
124120
- killer.once("close", (code) => {
124121
- if (code === 0) resolve();
124122
- else reject(/* @__PURE__ */ new Error(`taskkill exited with code ${code ?? "null"}`));
124123
- });
124124
- killer.unref();
124125
- });
124126
- }
124127
- try {
124128
- process.kill(-this.pid, signal ?? "SIGTERM");
124129
- } catch (error) {
124130
- const err = error;
124131
- if (err.code === "ESRCH") return Promise.resolve();
124132
- if (err.code === "EPERM") {
124133
- try {
124134
- this._child.kill(signal ?? "SIGTERM");
124135
- } catch {}
124136
- return Promise.resolve();
124137
- }
124138
- throw error;
124139
- }
124140
- return Promise.resolve();
124141
- }
124142
- };
124143
- let atomicWriteCounter = 0;
124144
- /**
124145
- * A JIAN implementation that directly interacts with the local filesystem.
124146
- *
124147
- * Note: LocalJian maintains its own per-instance working directory (`_cwd`)
124148
- * rather than mutating `process.cwd()`. This lets multiple LocalJian instances
124149
- * coexist with independent cwds (e.g. when switching contexts via
124150
- * `runWithJian`) without cross-polluting each other's relative-path resolution.
124151
- */
124152
- var LocalJian = class LocalJian {
124153
- name = "local";
124154
- osEnv;
124155
- _cwd;
124156
- _rootDir;
124157
- constructor(osEnv, cwd, rootDir) {
124158
- this._cwd = normalize$1(cwd ?? process.cwd());
124159
- this._rootDir = rootDir === void 0 ? void 0 : normalize$1(rootDir);
124160
- this.osEnv = osEnv;
124161
- }
124162
- /**
124163
- * Construct a fresh `LocalJian` after probing the host environment.
124164
- *
124165
- * Each call returns a new instance with its own `_cwd`; concurrent
124166
- * callers can therefore operate on independent working directories
124167
- * without polluting one another.
124168
- */
124169
- static async create(cwd, rootDir, env) {
124170
- return new LocalJian(env ?? await detectEnvironmentFromNode(), cwd, rootDir);
124171
- }
124172
- withCwd(cwd) {
124173
- return new LocalJian(this.osEnv, cwd, this._rootDir);
124174
- }
124175
- _resolvePath(path) {
124176
- const resolved = isAbsolute$1(path) ? normalize$1(path) : join$1(this._cwd, path);
124177
- this._assertWithinRoot(resolved);
124178
- return resolved;
124179
- }
124180
- _assertWithinRoot(resolvedPath) {
124181
- if (this._rootDir === void 0) return;
124182
- if (isWithinDirectory(resolvedPath, this._rootDir)) return;
124183
- throw new JianPathOutsideRootError(`Path outside allowed root directory: ${resolvedPath}`, resolvedPath, this._rootDir);
124184
- }
124185
- /** Resolve path for sandboxed operations — lexical check + realpath. */
124186
- async _resolveSandboxedPath(path) {
124187
- const lexical = isAbsolute$1(path) ? normalize$1(path) : join$1(this._cwd, path);
124188
- if (!isWithinDirectory(lexical, this._rootDir)) throw new JianPathOutsideRootError(`Path outside allowed root directory: ${lexical}`, lexical, this._rootDir);
124189
- const realPath = await realpath(lexical);
124190
- if (!isWithinDirectory(realPath, await realpath(this._rootDir))) throw new JianPathOutsideRootError(`Path outside allowed root directory (via symlink): ${lexical}`, lexical, this._rootDir);
124191
- return realPath;
124192
- }
124193
- pathClass() {
124194
- return isWindows ? "win32" : "posix";
124195
- }
124196
- normpath(path) {
124197
- return normalize$1(path);
124198
- }
124199
- gethome() {
124200
- return normalize$1(homedir());
124201
- }
124202
- getcwd() {
124203
- return this._cwd;
124204
- }
124205
- /**
124206
- * Change the working directory of this LocalJian instance.
124207
- *
124208
- * Unlike Python's `os.chdir`, this is instance-scoped and never touches
124209
- * `process.cwd()`. Child processes spawned via {@link exec} inherit this
124210
- * instance's `_cwd`; concurrent LocalJian instances each carry their own
124211
- * independent cwd. If you need Python-compatible process-global cwd,
124212
- * call `process.chdir(x)` directly.
124213
- */
124214
- async chdir(path) {
124215
- const resolved = this._rootDir !== void 0 ? await this._resolveSandboxedPath(path) : this._resolvePath(path);
124216
- if (!(await stat(resolved)).isDirectory()) throw new Error(`Not a directory: ${resolved}`);
124217
- this._cwd = resolved;
124218
- }
124219
- async realpath(path, options) {
124220
- const lexical = this._resolvePath(path);
124221
- try {
124222
- return normalize$1(await realpath(lexical));
124223
- } catch (error) {
124224
- const code = error.code;
124225
- if (!options?.allowMissing || code !== "ENOENT" && code !== "ENOTDIR") throw error;
124226
- }
124227
- const missingSegments = [];
124228
- let ancestor = lexical;
124229
- while (true) {
124230
- try {
124231
- await lstat(ancestor);
124232
- } catch (error) {
124233
- const code = error.code;
124234
- if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
124235
- const parent = dirname$2(ancestor);
124236
- if (parent === ancestor) throw error;
124237
- missingSegments.push(basename$1(ancestor));
124238
- ancestor = parent;
124239
- continue;
124240
- }
124241
- return normalize$1(join$1(normalize$1(await realpath(ancestor)), ...missingSegments.toReversed()));
124242
- }
124243
- }
124244
- async stat(path, options) {
124245
- const followSymlinks = options?.followSymlinks ?? true;
124246
- const resolved = this._rootDir !== void 0 && followSymlinks ? await this._resolveSandboxedPath(path) : this._resolvePath(path);
124247
- const s = followSymlinks ? await stat(resolved) : await lstat(resolved);
124248
- return {
124249
- stMode: s.mode,
124250
- stIno: s.ino,
124251
- stDev: s.dev,
124252
- stNlink: s.nlink,
124253
- stUid: s.uid,
124254
- stGid: s.gid,
124255
- stSize: s.size,
124256
- stAtime: s.atimeMs / 1e3,
124257
- stMtime: s.mtimeMs / 1e3,
124258
- stCtime: isWindows ? s.birthtimeMs / 1e3 : s.ctimeMs / 1e3
124259
- };
124260
- }
124261
- async *iterdir(path) {
124262
- const resolved = this._resolvePath(path);
124263
- const entries = await readdir(resolved);
124264
- for (const entry of entries) yield join$1(resolved, entry);
124265
- }
124266
- async *glob(path, pattern, options) {
124267
- const resolved = this._resolvePath(path);
124268
- const caseSensitive = options?.caseSensitive ?? true;
124269
- const physicalAllowedRoots = options?.allowedRoots === void 0 ? void 0 : await Promise.all(options.allowedRoots.map((root) => this.realpath(root, { allowMissing: true })));
124270
- if (!await this._isWithinPhysicalRoots(resolved, physicalAllowedRoots)) return;
124271
- const patternParts = pattern.split("/");
124272
- const initVisited = /* @__PURE__ */ new Set();
124273
- try {
124274
- const rootKey = cycleKey(await stat(resolved));
124275
- if (rootKey !== null) initVisited.add(rootKey);
124276
- } catch {}
124277
- yield* this._globWalk(resolved, patternParts, caseSensitive, initVisited, physicalAllowedRoots);
124278
- }
124279
- async *_globWalk(basePath, patternParts, caseSensitive, visited, physicalAllowedRoots) {
124280
- if (!await this._isWithinPhysicalRoots(basePath, physicalAllowedRoots)) return;
124281
- if (patternParts.length === 0) return;
124282
- const [currentPattern, ...remainingParts] = patternParts;
124283
- if (currentPattern === "**") {
124284
- if (remainingParts.length > 0) yield* this._globWalk(basePath, remainingParts, caseSensitive, visited, physicalAllowedRoots);
124285
- else yield basePath;
124286
- let entries;
124287
- try {
124288
- entries = await readdir(basePath);
124289
- } catch {
124290
- return;
124291
- }
124292
- for (const entry of entries) {
124293
- const fullPath = join$1(basePath, entry);
124294
- if (this._rootDir && !isWithinDirectory(fullPath, this._rootDir)) continue;
124295
- let entryStat;
124296
- try {
124297
- entryStat = await stat(fullPath);
124298
- } catch {
124299
- continue;
124300
- }
124301
- if (entryStat.isDirectory()) {
124302
- const key = cycleKey(entryStat);
124303
- if (key !== null && visited.has(key)) continue;
124304
- yield* this._globWalk(fullPath, patternParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited, physicalAllowedRoots);
124305
- } else if (remainingParts.length === 0 && await this._isWithinPhysicalRoots(fullPath, physicalAllowedRoots)) yield fullPath;
124306
- }
124307
- } else {
124308
- const regex = globPatternToRegex(currentPattern ?? "", caseSensitive);
124309
- let entries;
124310
- try {
124311
- entries = await readdir(basePath);
124312
- } catch {
124313
- return;
124314
- }
124315
- for (const entry of entries) {
124316
- if (!regex.test(entry)) continue;
124317
- const fullPath = join$1(basePath, entry);
124318
- if (this._rootDir && !isWithinDirectory(fullPath, this._rootDir)) continue;
124319
- if (remainingParts.length === 0) {
124320
- if (await this._isWithinPhysicalRoots(fullPath, physicalAllowedRoots)) yield fullPath;
124321
- } else {
124322
- let entryStat;
124323
- try {
124324
- entryStat = await stat(fullPath);
124325
- } catch {
124326
- continue;
124327
- }
124328
- if (entryStat.isDirectory()) {
124329
- const key = cycleKey(entryStat);
124330
- if (key !== null && visited.has(key)) continue;
124331
- yield* this._globWalk(fullPath, remainingParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited, physicalAllowedRoots);
124332
- }
124333
- }
124334
- }
124335
- }
124336
- }
124337
- async _isWithinPhysicalRoots(path, physicalAllowedRoots) {
124338
- if (physicalAllowedRoots === void 0) return true;
124339
- try {
124340
- const physicalPath = normalize$1(await realpath(path));
124341
- return physicalAllowedRoots.some((root) => isWithinDirectory(physicalPath, root));
124342
- } catch {
124343
- return false;
124344
- }
124345
- }
124346
- async readBytes(path, n) {
124347
- const resolved = this._resolvePath(path);
124348
- if (n === void 0) return Buffer.from(await readFile(resolved));
124349
- const fh = await open(resolved, "r");
124350
- try {
124351
- const buf = Buffer.alloc(n);
124352
- const { bytesRead } = await fh.read(buf, 0, n, 0);
124353
- return buf.subarray(0, bytesRead);
124354
- } finally {
124355
- await fh.close();
124356
- }
124357
- }
124358
- async readText(path, options) {
124359
- const resolved = this._resolvePath(path);
124360
- const encoding = options?.encoding ?? "utf-8";
124361
- const errors = options?.errors ?? "strict";
124362
- return decodeTextWithErrors(await readFile(resolved), encoding, errors);
124363
- }
124364
- async *readLines(path, options) {
124365
- const stream = createReadStream(this._resolvePath(path), {
124366
- encoding: options?.encoding ?? "utf-8",
124367
- highWaterMark: 64 * 1024
124368
- });
124369
- let remainder = "";
124370
- try {
124371
- for await (const chunk of stream) {
124372
- const lines = (remainder + chunk).split("\n");
124373
- remainder = lines.pop() ?? "";
124374
- for (const line of lines) yield line + "\n";
124375
- }
124376
- } finally {
124377
- stream.destroy();
124378
- }
124379
- if (remainder !== "") yield remainder;
124380
- }
124381
- async writeBytes(path, data) {
124382
- await writeFile(this._resolvePath(path), data);
124383
- return data.length;
124384
- }
124385
- async writeText(path, data, options) {
124386
- const resolved = this._resolvePath(path);
124387
- const encoding = options?.encoding ?? "utf-8";
124388
- if ((options?.mode ?? "w") === "a") await appendFile(resolved, data, encoding);
124389
- else await writeFile(resolved, data, encoding);
124390
- return data.length;
124391
- }
124392
- async writeTextAtomic(path, data, options) {
124393
- const resolved = this._resolvePath(path);
124394
- const encoding = options?.encoding ?? "utf-8";
124395
- const tmpPath = `${resolved}.${process.pid}.${Date.now().toString(36)}.${(atomicWriteCounter++).toString(36)}.tmp`;
124396
- await writeFile(tmpPath, data, encoding);
124397
- await rename(tmpPath, resolved);
124398
- return data.length;
124399
- }
124400
- async mkdir(path, options) {
124401
- const resolved = this._resolvePath(path);
124402
- const parents = options?.parents ?? false;
124403
- const existOk = options?.existOk ?? false;
124404
- if (parents) {
124405
- if (!existOk) try {
124406
- if ((await stat(resolved)).isDirectory()) throw new JianFileExistsError(`${resolved} already exists`);
124407
- } catch (error) {
124408
- if (error instanceof JianFileExistsError) throw error;
124409
- if (error.code !== "ENOENT") throw error;
124410
- }
124411
- await mkdir(resolved, { recursive: true });
124412
- return;
124413
- }
124414
- try {
124415
- await mkdir(resolved);
124416
- } catch (error) {
124417
- if (existOk && error instanceof Error && "code" in error && error.code === "EEXIST") {
124418
- if (!(await stat(resolved)).isDirectory()) throw new JianFileExistsError(`${resolved} already exists but is not a directory`);
124419
- return;
124420
- }
124421
- throw error;
124422
- }
124423
- }
124424
- async exec(...args) {
124425
- return this.execWithEnv(args, void 0);
124426
- }
124427
- async execWithEnv(args, env) {
124428
- const command = args[0];
124429
- if (command === void 0) throw new Error("LocalJian.execWithEnv(): at least one argument (the command to run) is required.");
124430
- const child = spawn(command, args.slice(1), {
124431
- cwd: this._cwd,
124432
- stdio: [
124433
- "pipe",
124434
- "pipe",
124435
- "pipe"
124436
- ],
124437
- detached: !isWindows,
124438
- env: buildSafeEnv(env)
124439
- });
124440
- try {
124441
- await waitForSpawn(child);
124442
- } catch (error) {
124443
- if (error instanceof Error) throw new JianExecError(`Failed to spawn ${command}: ${error.message}`, command, error.code);
124444
- throw error;
124445
- }
124446
- return new LocalProcess(child);
124447
- }
124448
- };
124449
- function waitForSpawn(child) {
124450
- return new Promise((resolve, reject) => {
124451
- const onSpawn = () => {
124452
- child.off("error", onError);
124453
- resolve();
124454
- };
124455
- const onError = (err) => {
124456
- child.off("spawn", onSpawn);
124457
- reject(err);
124458
- };
124459
- child.once("spawn", onSpawn);
124460
- child.once("error", onError);
124461
- });
124462
- }
124463
- new AsyncLocalStorage();
124464
- //#endregion
124465
125130
  //#region ../../packages/agent-core/src/rpc/core-impl.ts
124466
125131
  const ENV_CACHE_TTL_MS = 10080 * 60 * 1e3;
124467
125132
  /**
@@ -133927,7 +134592,7 @@ async function guidedGoalSetup(host) {
133927
134592
  host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
133928
134593
  return;
133929
134594
  }
133930
- const { TextInputDialogComponent } = await import("./text-input-dialog-DDUarM0_.mjs");
134595
+ const { TextInputDialogComponent } = await import("./text-input-dialog-DBgQwxaO.mjs");
133931
134596
  const initialDesc = await promptText(host, TextInputDialogComponent, {
133932
134597
  title: t("goal.setup_title_initial"),
133933
134598
  subtitle: t("goal.setup_desc_hint"),
@@ -133948,7 +134613,7 @@ async function guidedGoalSetup(host) {
133948
134613
  await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
133949
134614
  }
133950
134615
  async function showGoalConfigWizard(host, session, objective, replace) {
133951
- const { TextInputDialogComponent } = await import("./text-input-dialog-DDUarM0_.mjs");
134616
+ const { TextInputDialogComponent } = await import("./text-input-dialog-DBgQwxaO.mjs");
133952
134617
  const turnInput = await promptNumber(host, TextInputDialogComponent, {
133953
134618
  title: t("goal.wizard_title", { objective }),
133954
134619
  subtitle: t("goal.budget_turns_hint"),
@@ -140124,26 +140789,36 @@ async function writeUpdateCache(value, filePath = getUpdateStateFile()) {
140124
140789
  await writeJsonFile(filePath, UpdateCacheSchema, value);
140125
140790
  }
140126
140791
  //#endregion
140792
+ //#region src/utils/exec/npm.ts
140793
+ function npmLaunchPlan(args, platform = process.platform, comspec = process.env["ComSpec"]) {
140794
+ if (platform !== "win32") return {
140795
+ command: "npm",
140796
+ args,
140797
+ windowsVerbatimArguments: false,
140798
+ platform
140799
+ };
140800
+ return {
140801
+ command: comspec ?? "cmd.exe",
140802
+ args: [
140803
+ "/d",
140804
+ "/s",
140805
+ "/c",
140806
+ buildCmdCommandLine("npm.cmd", args)
140807
+ ],
140808
+ windowsVerbatimArguments: true,
140809
+ platform
140810
+ };
140811
+ }
140812
+ //#endregion
140127
140813
  //#region src/cli/update/cdn.ts
140128
140814
  const NPM_TIMEOUT_MS = 3e3;
140129
140815
  /**
140130
- * Resolve the npm executable name for the current platform.
140131
- *
140132
- * On Windows, `npm` is actually `npm.cmd` — a batch file. Node's child_process
140133
- * can execute `.cmd` files directly without `shell: true`, but only when the
140134
- * filename includes the `.cmd` extension. Using `'npm'` without `.cmd` would
140135
- * fail with ENOENT on Windows.
140136
- *
140137
- * We deliberately avoid `shell: true` because passing args alongside
140138
- * `shell: true` triggers Node's DEP0190 deprecation warning on every spawn.
140139
- */
140140
- function npmExecutable$1() {
140141
- return process.platform === "win32" ? "npm.cmd" : "npm";
140142
- }
140143
- /**
140144
140816
  * Query the latest published Scream Code version from the npm registry
140145
140817
  * via `npm view scream-code version`.
140146
140818
  *
140819
+ * The argv comes from `npmLaunchPlan`, so a Windows host goes through
140820
+ * `cmd.exe` — see there for why the `.cmd` shim cannot be spawned directly.
140821
+ *
140147
140822
  * **Throws** on any failure (network error, npm not in PATH, non-semver
140148
140823
  * output). Callers must catch — `refreshUpdateCache` deliberately lets the
140149
140824
  * error propagate so the existing cache stays intact instead of being
@@ -140152,13 +140827,16 @@ function npmExecutable$1() {
140152
140827
  * `execFileImpl` is injectable for tests; defaults to a promisified spawn.
140153
140828
  */
140154
140829
  async function fetchLatestVersionFromNpm(execFileImpl = execFile) {
140155
- const { stdout } = await promisify(execFileImpl)(npmExecutable$1(), [
140830
+ const execAsync = promisify(execFileImpl);
140831
+ const npm = npmLaunchPlan([
140156
140832
  "view",
140157
140833
  "scream-code",
140158
140834
  "version"
140159
- ], {
140835
+ ]);
140836
+ const { stdout } = await execAsync(npm.command, [...npm.args], {
140160
140837
  timeout: NPM_TIMEOUT_MS,
140161
- maxBuffer: 1024
140838
+ maxBuffer: 1024,
140839
+ windowsVerbatimArguments: npm.windowsVerbatimArguments
140162
140840
  });
140163
140841
  const raw = stdout.trim();
140164
140842
  if (valid(raw) === null) throw new Error(`npm view 返回的版本号不是合法 semver: ${JSON.stringify(raw)}`);
@@ -140198,15 +140876,6 @@ function selectUpdateTarget(currentVersion, latest) {
140198
140876
  * Network-error detection with user-friendly Chinese prompts.
140199
140877
  */
140200
140878
  const INSTALL_TIMEOUT_MS = 3e5;
140201
- /**
140202
- * Resolve the npm executable name for the current platform.
140203
- *
140204
- * On Windows, `npm` is `npm.cmd` — a batch file Node can spawn directly
140205
- * without `shell: true` (which would trigger DEP0190 when args are passed).
140206
- */
140207
- function npmExecutable() {
140208
- return process.platform === "win32" ? "npm.cmd" : "npm";
140209
- }
140210
140879
  const NETWORK_ERROR_PATTERNS = [
140211
140880
  /ETIMEDOUT/i,
140212
140881
  /ENOTFOUND/i,
@@ -140227,11 +140896,44 @@ const NETWORK_ERROR_PATTERNS = [
140227
140896
  function isNetworkError(message) {
140228
140897
  return NETWORK_ERROR_PATTERNS.some((p) => p.test(message));
140229
140898
  }
140230
- async function runInstallStep(cmd, args, cwd, label, timeoutMs = INSTALL_TIMEOUT_MS) {
140899
+ /**
140900
+ * Reap an install step whose timeout expired.
140901
+ *
140902
+ * On Windows the direct child is `cmd.exe` (see `npmLaunchPlan`), so signalling
140903
+ * it stops the wrapper and leaves the `npm` / `node` beneath it running — there
140904
+ * is no process group on Windows to signal instead. `killProcessTree` makes up
140905
+ * for that with `taskkill /F /T /PID`, which walks the tree. POSIX has no
140906
+ * wrapper in between, so the child itself is signalled exactly as before.
140907
+ *
140908
+ * Never rejects: the step has already failed with a timeout and the caller is
140909
+ * told so, and a reaper that could not run must not turn that into a different
140910
+ * error.
140911
+ */
140912
+ async function reapTimedOutStep(child, platform) {
140913
+ if (platform !== "win32") {
140914
+ child.kill("SIGTERM");
140915
+ return;
140916
+ }
140917
+ try {
140918
+ await killProcessTree(child.pid ?? 0, {
140919
+ signal: "SIGTERM",
140920
+ platform
140921
+ });
140922
+ } catch {}
140923
+ }
140924
+ /**
140925
+ * Run one install step. `launch` comes from `npmLaunchPlan`, so a Windows host
140926
+ * gets the `cmd.exe` command line its `.cmd` shim needs; `spawn` receives the
140927
+ * matching `windowsVerbatimArguments` flag because the line is already quoted.
140928
+ * `launch` also carries the platform, which is what the timeout teardown
140929
+ * branches on.
140930
+ */
140931
+ async function runInstallStep(launch, cwd, label, timeoutMs = INSTALL_TIMEOUT_MS) {
140231
140932
  return new Promise((resolve) => {
140232
- const child = spawn(cmd, args, {
140933
+ const child = spawn(launch.command, [...launch.args], {
140233
140934
  cwd,
140234
- stdio: "pipe"
140935
+ stdio: "pipe",
140936
+ windowsVerbatimArguments: launch.windowsVerbatimArguments
140235
140937
  });
140236
140938
  let stderr = "";
140237
140939
  let settled = false;
@@ -140239,14 +140941,14 @@ async function runInstallStep(cmd, args, cwd, label, timeoutMs = INSTALL_TIMEOUT
140239
140941
  stderr += d.toString();
140240
140942
  });
140241
140943
  const timer = setTimeout(() => {
140242
- if (!settled) {
140243
- settled = true;
140244
- child.kill("SIGTERM");
140944
+ if (settled) return;
140945
+ settled = true;
140946
+ reapTimedOutStep(child, launch.platform).then(() => {
140245
140947
  resolve({
140246
140948
  ok: false,
140247
140949
  message: `${label}${t("update.timeout")}\n` + t("update.network_hint")
140248
140950
  });
140249
- }
140951
+ });
140250
140952
  }, timeoutMs);
140251
140953
  const finalize = (result) => {
140252
140954
  if (settled) return;
@@ -140301,7 +141003,7 @@ async function handleUpdateCommand(host) {
140301
141003
  }
140302
141004
  host.showStatus(t("update.updating", { version: target.version }));
140303
141005
  host.showStatus(t("update.npm_install"));
140304
- const result = await runInstallStep(npmExecutable(), installLatestArgs(), void 0, t("update.install_label"));
141006
+ const result = await runInstallStep(npmLaunchPlan(installLatestArgs()), void 0, t("update.install_label"));
140305
141007
  if (!result.ok) {
140306
141008
  host.showError(`❌ ${result.message}`);
140307
141009
  return;
@@ -145652,4 +146354,4 @@ async function handleBuiltInSlashCommand(host, name, args) {
145652
146354
  }
145653
146355
  }
145654
146356
  //#endregion
145655
- export { handleExportDebugZipCommand as $, encodeWorkDirKey as $n, lerpGradient as $t, highlightLines as A, embeddingFailureDetail as An, isTurnElapsedEnabled as At, ActivityGroupComponent as B, detectShellEnvironment as Bn, ENABLE_TERMINAL_THEME_REPORTING as Bt, handleCcCommand as C, getLlmNotSetMessage as Cn, clearInfoPanelState as Ct, ToolCallComponent as D, sortSlashCommands as Dn, createEditorTheme as Dt, UserMessageComponent as E, BUILTIN_SLASH_COMMANDS as En, resolveThemeSync as Et, StatusMessageComponent as F, PULSE_WAVE_FRAMES as Fn, detectTerminalTheme as Ft, AgentGroupComponent as G, CLI_COMMAND_NAME as Gn, QUERY_TERMINAL_THEME as Gt, estimateTokens as H, getInputHistoryFile as Hn, OSC11_RESPONSE as Ht, replaceTabs as I, TuiConfigParseError as In, parseOsc11BackgroundTheme as It, getBreathingFrame as J, PRODUCT_NAME as Jn, TERMINAL_THEME_DARK as Jt, WelcomeComponent as K, CLI_UI_MODE as Kn, TERMINAL_FOCUS_IN as Kt, sanitizeShellOutput as L, TuiLikePreferencesSchema as Ln, DISABLE_TERMINAL_FOCUS_REPORTING as Lt, CachedContainer as M, hasEmbeddingModelCache as Mn, createThemeStyles as Mt, ThinkingComponent as N, startManualEmbeddingDownload as Nn, contrastTextHex as Nt, renderDiffLines as O, isExperimentalFlagEnabled as On, createMarkdownTheme as Ot, NoticeMessageComponent as P, PIXEL_PULSE_FRAMES as Pn, getColorPalette as Pt, refineGoal as Q, ScreamHarness as Qn, FooterComponent as Qt, SkillActivationComponent as R, loadTuiConfig as Rn, DISABLE_TERMINAL_THEME_REPORTING as Rt, readJsonlFile as S, getCtrlDHint as Sn, supportsBalance as St, handleRevokeCommand as T, buildSkillSlashCommands as Tn, showUsage as Tt, getSharedSpeedTracker as U, getLogDir as Un, OSC11_RESPONSE_PREFIX as Ut, charsForTokenBudget as V, getDataDir as Vn, OSC11_QUERY as Vt, BackgroundAgentStatusComponent as W, detectInstallSource as Wn, OSC11_RESPONSE_PREFIX_NO_ESC as Wt, CompactionComponent as X, fetchCatalog as Xn, isBusy as Xt, resetBreathingClock as Y, DEFAULT_CATALOG_URL as Yn, TERMINAL_THEME_LIGHT as Yt, clearGoalState as Z, saveCatalogCache as Zn, isStreaming as Zt, handleUpdateCommand as _, EXIT_CONFIRM_WINDOW_MS as _n, handleYoloCommand as _t, syncDiagramCapabilityPrompt as a, printableChar as an, resolveScreamHome as ar, toTerminalHyperlink as at, readUpdateCache as b, TIP_ROTATION_INTERVAL_MS as bn, showSettingsSelector as bt, disposeChildren as c, appendStreamingArgsPreview as cn, resolveGlobalLogPath as cr, handleAutoCommand as ct, isPlanExpandable as d, isTodoItemShape as dn, ErrorCodes as dr, handleEditorCommand as dt, SubagentSlots as en, normalizeWorkDir as er, handleExportMdCommand as et, MoonLoader as f, parseStreamingArgs as fn, SCREAM_ERROR_INFO as fr, handleFusionPlanCommand as ft, handleMcpCommand as g, EMPTY_SESSION_HINT_URL as gn, handleWolfpackCommand as gt, handleChannelCommand as h, truncateErrorMessage as hn, handleThemeCommand as ht, buildRoleAdditionalText as i, handleLogoutCommand as in, MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE as ir, isInternalMessage as it, langFromPath as j, getKnowledgeStore as jn, toggleEmptySessionHint as jt, renderDiffLinesClustered as k, setExperimentalFlags as kn, isEmptySessionHintDismissed as kt, hasDispose as l, argsRecord as ln, isScreamError as lr, handleBotCommand as lt, handleMemoryCommand as m, stringValue as mn, handlePlanCommand as mt, clearEvalPanelState as n, handleSearchCommand as nn, readSessionIndex as nr, handleInitCommand as nt, handleExtensionCommand as o, STATUS_BULLET as on, flushDiagnosticLogs as or, changeThinkingLevel as ot, formatMemoryMemoForInjection as p, serializeToolResultOutput as pn, handleModelCommand as pt, BREATHE_CYCLE_MS as q, CLI_USER_AGENT_PRODUCT as qn, TERMINAL_FOCUS_OUT as qt, openUrl as r, handleConnectCommand as rn, removeSessionIndexEntry as rr, handleTitleCommand as rt, handleSkillCommand as s, appendSessionHeaderHint as sn, log as sr, getModelCycleLevel as st, dispatchInput as t, handleTraceCommand as tn, appendSessionIndexEntry as tr, handleForkCommand as tt, isExpandable as u, formatErrorMessage as un, isOrphanedToolCallError as ur, handleCompactCommand as ut, selectUpdateTarget as v, MAIN_AGENT_ID$1 as vn, showModelPicker as vt, getDaemonInstructions as w, getNoActiveSessionMessage as wn, showStatusReport as wt, appendJsonlLine as x, getCtrlCHint as xn, refreshProviderBalance as xt, refreshUpdateCache as y, SESSION_TIPS as yn, showPermissionPicker as yt, AssistantMessageComponent as z, saveTuiConfig as zn, ENABLE_TERMINAL_FOCUS_REPORTING as zt };
146357
+ export { handleExportDebugZipCommand as $, encodeWorkDirKey as $n, lerpGradient as $t, highlightLines as A, embeddingFailureDetail as An, isTurnElapsedEnabled as At, ActivityGroupComponent as B, detectShellEnvironment as Bn, ENABLE_TERMINAL_THEME_REPORTING as Bt, handleCcCommand as C, getLlmNotSetMessage as Cn, clearInfoPanelState as Ct, ToolCallComponent as D, sortSlashCommands as Dn, createEditorTheme as Dt, UserMessageComponent as E, BUILTIN_SLASH_COMMANDS as En, resolveThemeSync as Et, StatusMessageComponent as F, PULSE_WAVE_FRAMES as Fn, detectTerminalTheme as Ft, AgentGroupComponent as G, CLI_COMMAND_NAME as Gn, QUERY_TERMINAL_THEME as Gt, estimateTokens as H, getInputHistoryFile as Hn, OSC11_RESPONSE as Ht, replaceTabs as I, TuiConfigParseError as In, parseOsc11BackgroundTheme as It, getBreathingFrame as J, PRODUCT_NAME as Jn, TERMINAL_THEME_DARK as Jt, WelcomeComponent as K, CLI_UI_MODE as Kn, TERMINAL_FOCUS_IN as Kt, sanitizeShellOutput as L, TuiLikePreferencesSchema as Ln, DISABLE_TERMINAL_FOCUS_REPORTING as Lt, CachedContainer as M, hasEmbeddingModelCache as Mn, createThemeStyles as Mt, ThinkingComponent as N, startManualEmbeddingDownload as Nn, contrastTextHex as Nt, renderDiffLines as O, isExperimentalFlagEnabled as On, createMarkdownTheme as Ot, NoticeMessageComponent as P, PIXEL_PULSE_FRAMES as Pn, getColorPalette as Pt, refineGoal as Q, ScreamHarness as Qn, FooterComponent as Qt, SkillActivationComponent as R, loadTuiConfig as Rn, DISABLE_TERMINAL_THEME_REPORTING as Rt, readJsonlFile as S, getCtrlDHint as Sn, supportsBalance as St, handleRevokeCommand as T, buildSkillSlashCommands as Tn, showUsage as Tt, getSharedSpeedTracker as U, getLogDir as Un, OSC11_RESPONSE_PREFIX as Ut, charsForTokenBudget as V, getDataDir as Vn, OSC11_QUERY as Vt, BackgroundAgentStatusComponent as W, detectInstallSource as Wn, OSC11_RESPONSE_PREFIX_NO_ESC as Wt, CompactionComponent as X, fetchCatalog as Xn, isBusy as Xt, resetBreathingClock as Y, DEFAULT_CATALOG_URL as Yn, TERMINAL_THEME_LIGHT as Yt, clearGoalState as Z, saveCatalogCache as Zn, isStreaming as Zt, handleUpdateCommand as _, EXIT_CONFIRM_WINDOW_MS as _n, handleYoloCommand as _t, syncDiagramCapabilityPrompt as a, printableChar as an, isRealUserPrompt as ar, toTerminalHyperlink as at, readUpdateCache as b, TIP_ROTATION_INTERVAL_MS as bn, showSettingsSelector as bt, disposeChildren as c, appendStreamingArgsPreview as cn, log as cr, handleAutoCommand as ct, isPlanExpandable as d, isTodoItemShape as dn, isOrphanedToolCallError as dr, handleEditorCommand as dt, SubagentSlots as en, normalizeWorkDir as er, handleExportMdCommand as et, MoonLoader as f, parseStreamingArgs as fn, ErrorCodes as fr, handleFusionPlanCommand as ft, handleMcpCommand as g, EMPTY_SESSION_HINT_URL as gn, handleWolfpackCommand as gt, handleChannelCommand as h, truncateErrorMessage as hn, handleThemeCommand as ht, buildRoleAdditionalText as i, handleLogoutCommand as in, MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE as ir, isInternalMessage as it, langFromPath as j, getKnowledgeStore as jn, toggleEmptySessionHint as jt, renderDiffLinesClustered as k, setExperimentalFlags as kn, isEmptySessionHintDismissed as kt, hasDispose as l, argsRecord as ln, resolveGlobalLogPath as lr, handleBotCommand as lt, handleMemoryCommand as m, stringValue as mn, handlePlanCommand as mt, clearEvalPanelState as n, handleSearchCommand as nn, readSessionIndex as nr, handleInitCommand as nt, handleExtensionCommand as o, STATUS_BULLET as on, resolveScreamHome as or, changeThinkingLevel as ot, formatMemoryMemoForInjection as p, serializeToolResultOutput as pn, SCREAM_ERROR_INFO as pr, handleModelCommand as pt, BREATHE_CYCLE_MS as q, CLI_USER_AGENT_PRODUCT as qn, TERMINAL_FOCUS_OUT as qt, openUrl as r, handleConnectCommand as rn, removeSessionIndexEntry as rr, handleTitleCommand as rt, handleSkillCommand as s, appendSessionHeaderHint as sn, flushDiagnosticLogs as sr, getModelCycleLevel as st, dispatchInput as t, handleTraceCommand as tn, appendSessionIndexEntry as tr, handleForkCommand as tt, isExpandable as u, formatErrorMessage as un, isScreamError as ur, handleCompactCommand as ut, selectUpdateTarget as v, MAIN_AGENT_ID$1 as vn, showModelPicker as vt, getDaemonInstructions as w, getNoActiveSessionMessage as wn, showStatusReport as wt, appendJsonlLine as x, getCtrlCHint as xn, refreshProviderBalance as xt, refreshUpdateCache as y, SESSION_TIPS as yn, showPermissionPicker as yt, AssistantMessageComponent as z, saveTuiConfig as zn, ENABLE_TERMINAL_FOCUS_REPORTING as zt };