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.
- package/README.md +1 -1
- package/dist/{app-GeQQHNLx.mjs → app-BJSym649.mjs} +62 -46
- package/dist/{dispatch-CNitkOqX.mjs → dispatch-CIOuINvn.mjs} +1810 -1108
- package/dist/{dispatch-Du8ddILK.mjs → dispatch-ChbwC2I5.mjs} +1 -1
- package/dist/main.mjs +1 -1
- package/dist/{src-CXmZg1VT.mjs → src-BhZujafw.mjs} +30 -4
- package/dist/{src-DIAKpkXO.mjs → src-CUURS-i6.mjs} +1 -1
- package/dist/{text-input-dialog-DDUarM0_.mjs → text-input-dialog-DBgQwxaO.mjs} +1 -1
- package/package.json +5 -2
- package/dist/{text-input-dialog-6v2667rm.mjs → text-input-dialog-XT3cOEjD.mjs} +1 -1
|
@@ -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
|
|
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-
|
|
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,
|
|
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
|
|
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
|
|
50864
|
-
const nb = normalize
|
|
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-
|
|
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
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
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
|
|
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
|
|
69047
|
-
const normBase = normalize
|
|
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 ??
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
72160
|
-
|
|
72161
|
-
|
|
72162
|
-
|
|
72163
|
-
|
|
72164
|
-
|
|
72165
|
-
|
|
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
|
-
|
|
72168
|
-
|
|
72169
|
-
|
|
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
|
|
72172
|
-
const
|
|
72173
|
-
if (
|
|
72174
|
-
|
|
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(
|
|
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
|
|
73038
|
-
const normBase = normalize
|
|
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
|
|
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
|
|
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
|
|
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
|
|
73337
|
+
if (m?.[1] !== void 0) return normalize(m[1]);
|
|
73138
73338
|
const separatorIndex = noLineNumberContentSeparatorIndex(line, pathClass);
|
|
73139
|
-
return separatorIndex > 0 ? normalize
|
|
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
|
|
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
|
|
76470
|
-
*
|
|
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
|
-
|
|
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(
|
|
76618
|
-
if (
|
|
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.
|
|
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
|
-
|
|
76663
|
-
|
|
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.
|
|
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()) ?
|
|
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
|
-
*
|
|
78110
|
-
*
|
|
78111
|
-
*
|
|
78112
|
-
*
|
|
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 (
|
|
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
|
|
78946
|
-
* message in the compacted history (
|
|
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
|
|
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
|
|
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
|
-
|
|
81659
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
82329
|
-
const TODO_LIST_REMINDER_TURNS_BETWEEN_REMINDERS =
|
|
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
|
-
* -
|
|
82337
|
-
*
|
|
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(
|
|
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
|
-
|
|
82876
|
-
|
|
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
|
|
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
|
|
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 (!
|
|
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(
|
|
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
|
|
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,
|
|
100465
|
-
*
|
|
100466
|
-
*
|
|
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 ===
|
|
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-
|
|
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-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
140243
|
-
|
|
140244
|
-
|
|
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(
|
|
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,
|
|
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 };
|