blun-king-cli 9.1.600 → 9.1.601

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/blun.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:c0b3db57102f82b976cc669375212221a4788318dbd6d1bfc28e943f7bf649db
2
+ // BLUN_BUILD_INPUT_SHA256:b5b19f76ccc092b4eba456b9da34bbda10f5c0717713caa03a28337eab2cac02
3
3
  import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
4
4
  import { dirname as __cjsShimDirname } from 'node:path';
5
5
  const __filename = __cjsShimFileURLToPath(import.meta.url);
@@ -181000,7 +181000,10 @@ function applyCompletionBudgetWithDetails(args) {
181000
181000
  });
181001
181001
  if (args.retry !== void 0) cap = Math.max(args.retry.minimumCompletionTokens, Math.ceil(cap * args.retry.multiplier));
181002
181002
  const maxContextTokens = args.capability?.max_context_tokens;
181003
- if (args.usedContextTokens !== void 0 && maxContextTokens !== void 0 && maxContextTokens > 0) cap = Math.max(MIN_FLOOR, Math.min(cap, maxContextTokens - args.usedContextTokens));
181003
+ if (args.usedContextTokens !== void 0 && maxContextTokens !== void 0 && maxContextTokens > 0) {
181004
+ const headroom = Math.min(2048, Math.ceil(maxContextTokens * .01));
181005
+ cap = Math.max(MIN_FLOOR, Math.min(cap, maxContextTokens - args.usedContextTokens - headroom));
181006
+ }
181004
181007
  return {
181005
181008
  provider: args.provider.withMaxCompletionTokens(cap, {
181006
181009
  usedContextTokens: args.usedContextTokens,
@@ -235215,6 +235218,9 @@ var init_engine = __esmMin((() => {
235215
235218
  for (const [event, hooks] of this.byEvent.entries()) result[event] = hooks.length;
235216
235219
  return result;
235217
235220
  }
235221
+ hasMatchingHook(event, matcherValue) {
235222
+ return this.matchingHooks(event, matcherValue).length > 0;
235223
+ }
235218
235224
  closeAdmission() {
235219
235225
  this.admissionClosed = true;
235220
235226
  }
@@ -264967,6 +264973,1381 @@ var init_tool_result_budget = __esmMin((() => {
264967
264973
  TOOL_RESULT_PREVIEW_CHARS = 2e3;
264968
264974
  }));
264969
264975
  //#endregion
264976
+ //#region ../../packages/kaos/src/errors.ts
264977
+ var KaosError, KaosFileExistsError, KaosShellNotFoundError;
264978
+ var init_errors$1 = __esmMin((() => {
264979
+ KaosError = class extends Error {
264980
+ constructor(message) {
264981
+ super(message);
264982
+ this.name = "KaosError";
264983
+ }
264984
+ };
264985
+ KaosFileExistsError = class extends KaosError {
264986
+ constructor(message) {
264987
+ super(message);
264988
+ this.name = "KaosFileExistsError";
264989
+ }
264990
+ };
264991
+ KaosShellNotFoundError = class extends KaosError {
264992
+ constructor(message) {
264993
+ super(message);
264994
+ this.name = "KaosShellNotFoundError";
264995
+ }
264996
+ };
264997
+ }));
264998
+ //#endregion
264999
+ //#region ../../packages/kaos/src/environment.ts
265000
+ /**
265001
+ * Environment — cross-platform probe of OS / shell.
265002
+ *
265003
+ * Detection is a pure function of injected probes (`platform` / `arch` /
265004
+ * `release` / `env` / `isFile` / `execFileText`) so the same suite runs
265005
+ * identically on any host OS. `detectEnvironmentFromNode()` bundles the Node
265006
+ * defaults for production callers.
265007
+ *
265008
+ * On Windows the probe expects Git Bash (the canonical POSIX shell that
265009
+ * ships with Git for Windows). If it cannot be located the function
265010
+ * throws `KaosShellNotFoundError`; the SDK layer can wrap that into a
265011
+ * user-facing install hint. Set `BLUN_SHELL_PATH` to override.
265012
+ */
265013
+ function resolveOsKind(platform) {
265014
+ switch (platform) {
265015
+ case "darwin": return "macOS";
265016
+ case "linux": return "Linux";
265017
+ case "win32": return "Windows";
265018
+ default: return platform;
265019
+ }
265020
+ }
265021
+ async function detectEnvironment(deps) {
265022
+ const osKind = resolveOsKind(deps.platform);
265023
+ const osArch = deps.arch;
265024
+ const osVersion = deps.release;
265025
+ if (deps.platform === "win32") return {
265026
+ osKind,
265027
+ osArch,
265028
+ osVersion,
265029
+ shellName: "bash",
265030
+ shellPath: await locateWindowsGitBash(deps)
265031
+ };
265032
+ const candidates = [
265033
+ "/bin/bash",
265034
+ "/usr/bin/bash",
265035
+ "/usr/local/bin/bash"
265036
+ ];
265037
+ let found;
265038
+ for (const p of candidates) if (await deps.isFile(p)) {
265039
+ found = p;
265040
+ break;
265041
+ }
265042
+ if (found !== void 0) return {
265043
+ osKind,
265044
+ osArch,
265045
+ osVersion,
265046
+ shellName: "bash",
265047
+ shellPath: found
265048
+ };
265049
+ return {
265050
+ osKind,
265051
+ osArch,
265052
+ osVersion,
265053
+ shellName: "sh",
265054
+ shellPath: "/bin/sh"
265055
+ };
265056
+ }
265057
+ async function locateWindowsGitBash(deps) {
265058
+ const checked = [];
265059
+ const override = deps.env["BLUN_SHELL_PATH"]?.trim();
265060
+ if (override !== void 0 && override.length > 0) {
265061
+ checked.push(override);
265062
+ if (await deps.isFile(override)) return override;
265063
+ }
265064
+ const gitExecutables = await findExecutablesOnPath("git.exe", deps.env["PATH"], deps.platform, deps.isFile);
265065
+ for (const gitExe of gitExecutables) {
265066
+ const inferred = gitBashCandidatesFromGitExe(gitExe);
265067
+ if (inferred !== void 0) for (const candidate of inferred) {
265068
+ checked.push(candidate);
265069
+ if (await deps.isFile(candidate)) return candidate;
265070
+ }
265071
+ const gitExecPath = await readGitExecPath(deps, gitExe);
265072
+ if (gitExecPath === void 0) continue;
265073
+ for (const candidate of gitBashCandidatesFromGitExecPath(gitExecPath)) {
265074
+ checked.push(candidate);
265075
+ if (await deps.isFile(candidate)) return candidate;
265076
+ }
265077
+ }
265078
+ const candidates = [
265079
+ "C:\\Program Files\\Git\\bin\\bash.exe",
265080
+ "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
265081
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
265082
+ "C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
265083
+ ];
265084
+ const localAppData = deps.env["LOCALAPPDATA"]?.trim();
265085
+ if (localAppData !== void 0 && localAppData.length > 0) {
265086
+ candidates.push(`${localAppData}\\Programs\\Git\\bin\\bash.exe`);
265087
+ candidates.push(`${localAppData}\\Programs\\Git\\usr\\bin\\bash.exe`);
265088
+ }
265089
+ for (const candidate of candidates) {
265090
+ checked.push(candidate);
265091
+ if (await deps.isFile(candidate)) return candidate;
265092
+ }
265093
+ throw new KaosShellNotFoundError(`Git Bash was not found on this Windows host. Install Git for Windows from https://gitforwindows.org/ or set BLUN_SHELL_PATH to a bash.exe. Checked: ${checked.join(", ")}.`);
265094
+ }
265095
+ async function readGitExecPath(deps, gitExe) {
265096
+ if (deps.platform === "win32" && !isAbsoluteWindowsPath(gitExe)) return void 0;
265097
+ const stdout = await deps.execFileText(gitExe, ["--exec-path"], GIT_EXEC_PATH_TIMEOUT_MS);
265098
+ if (stdout === void 0) return void 0;
265099
+ for (const line of stdout.split(/\r?\n/)) {
265100
+ const execPath = line.trim();
265101
+ if (execPath.length > 0) return execPath;
265102
+ }
265103
+ }
265104
+ function gitBashCandidatesFromGitExe(gitExe) {
265105
+ const normalizedGitExe = path$18.win32.normalize(normalizeWindowsPath(gitExe));
265106
+ const gitDir = path$18.win32.dirname(normalizedGitExe);
265107
+ const gitDirName = path$18.win32.basename(gitDir).toLowerCase();
265108
+ if (gitDirName !== "cmd" && gitDirName !== "bin") return;
265109
+ return gitBashCandidatesFromGitRoot(path$18.win32.dirname(gitDir));
265110
+ }
265111
+ function gitBashCandidatesFromGitExecPath(execPath) {
265112
+ const normalized = path$18.win32.normalize(normalizeWindowsPath(execPath));
265113
+ const parts = normalized.split("\\");
265114
+ for (let i = parts.length - 1; i >= 0; i -= 1) {
265115
+ const segment = parts[i]?.toLowerCase();
265116
+ if (segment === "mingw32" || segment === "mingw64") {
265117
+ const root = parts.slice(0, i).join("\\");
265118
+ if (root.length > 0) return gitBashCandidatesFromGitRoot(root);
265119
+ }
265120
+ }
265121
+ return gitBashCandidatesFromGitRoot(path$18.win32.join(normalized, "..", ".."));
265122
+ }
265123
+ function gitBashCandidatesFromGitRoot(root) {
265124
+ return [path$18.win32.normalize(path$18.win32.join(root, "bin", "bash.exe")), path$18.win32.normalize(path$18.win32.join(root, "usr", "bin", "bash.exe"))];
265125
+ }
265126
+ function normalizeWindowsPath(path) {
265127
+ return path.replaceAll("/", "\\");
265128
+ }
265129
+ function isAbsoluteWindowsPath(path) {
265130
+ return path$18.win32.isAbsolute(normalizeWindowsPath(path));
265131
+ }
265132
+ function dedupeWindowsPaths(paths) {
265133
+ const deduped = [];
265134
+ const seen = /* @__PURE__ */ new Set();
265135
+ for (const path of paths) {
265136
+ const key = normalizeWindowsPath(path).toLowerCase();
265137
+ if (seen.has(key)) continue;
265138
+ seen.add(key);
265139
+ deduped.push(path);
265140
+ }
265141
+ return deduped;
265142
+ }
265143
+ function detectEnvironmentFromNode() {
265144
+ if (detectedEnvironment !== void 0) return detectedEnvironment;
265145
+ const platform = process.platform;
265146
+ const env = process.env;
265147
+ const isFile = async (path) => {
265148
+ try {
265149
+ await access(path, constants.F_OK);
265150
+ return true;
265151
+ } catch {
265152
+ return false;
265153
+ }
265154
+ };
265155
+ detectedEnvironment = detectEnvironment({
265156
+ platform,
265157
+ arch: process.arch,
265158
+ release: nodeOs.release(),
265159
+ env,
265160
+ isFile,
265161
+ execFileText: execFileText$1
265162
+ });
265163
+ return detectedEnvironment;
265164
+ }
265165
+ async function findExecutablesOnPath(name, pathEnv, platform, isFile) {
265166
+ if (pathEnv === void 0 || pathEnv.length === 0) return [];
265167
+ const listSep = platform === "win32" ? ";" : ":";
265168
+ const dirSep = platform === "win32" ? "\\" : "/";
265169
+ const paths = [];
265170
+ for (const rawDir of pathEnv.split(listSep)) {
265171
+ const dir = rawDir.trim();
265172
+ if (dir.length === 0) continue;
265173
+ if (platform === "win32" && !isAbsoluteWindowsPath(dir)) continue;
265174
+ const candidate = dir.endsWith(dirSep) ? `${dir}${name}` : `${dir}${dirSep}${name}`;
265175
+ if (await isFile(candidate)) paths.push(candidate);
265176
+ }
265177
+ return platform === "win32" ? dedupeWindowsPaths(paths) : paths;
265178
+ }
265179
+ async function execFileText$1(file, args, timeoutMs) {
265180
+ return new Promise((resolve) => {
265181
+ execFile(file, [...args], {
265182
+ encoding: "utf8",
265183
+ timeout: timeoutMs,
265184
+ windowsHide: true
265185
+ }, (error, stdout) => {
265186
+ if (error !== null) {
265187
+ resolve(void 0);
265188
+ return;
265189
+ }
265190
+ resolve(stdout);
265191
+ });
265192
+ });
265193
+ }
265194
+ var GIT_EXEC_PATH_TIMEOUT_MS, detectedEnvironment;
265195
+ var init_environment$1 = __esmMin((() => {
265196
+ init_errors$1();
265197
+ GIT_EXEC_PATH_TIMEOUT_MS = 5e3;
265198
+ }));
265199
+ //#endregion
265200
+ //#region ../../packages/kaos/src/internal.ts
265201
+ function isUtf8Continuation(byte) {
265202
+ return byte >= 128 && byte <= 191;
265203
+ }
265204
+ function decodeUtf8Ignore(data) {
265205
+ let output = "";
265206
+ let i = 0;
265207
+ while (i < data.length) {
265208
+ const b0 = data[i];
265209
+ if (b0 === void 0) break;
265210
+ if (b0 <= 127) {
265211
+ output += String.fromCodePoint(b0);
265212
+ i += 1;
265213
+ continue;
265214
+ }
265215
+ if (b0 >= 194 && b0 <= 223) {
265216
+ const b1 = data[i + 1];
265217
+ if (b1 !== void 0 && isUtf8Continuation(b1)) {
265218
+ output += String.fromCodePoint((b0 & 31) << 6 | b1 & 63);
265219
+ i += 2;
265220
+ continue;
265221
+ }
265222
+ i += 1;
265223
+ continue;
265224
+ }
265225
+ if (b0 >= 224 && b0 <= 239) {
265226
+ const b1 = data[i + 1];
265227
+ const b2 = data[i + 2];
265228
+ 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)) {
265229
+ output += String.fromCodePoint((b0 & 15) << 12 | (b1 & 63) << 6 | b2 & 63);
265230
+ i += 3;
265231
+ continue;
265232
+ }
265233
+ i += 1;
265234
+ continue;
265235
+ }
265236
+ if (b0 >= 240 && b0 <= 244) {
265237
+ const b1 = data[i + 1];
265238
+ const b2 = data[i + 2];
265239
+ const b3 = data[i + 3];
265240
+ 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)) {
265241
+ output += String.fromCodePoint((b0 & 7) << 18 | (b1 & 63) << 12 | (b2 & 63) << 6 | b3 & 63);
265242
+ i += 4;
265243
+ continue;
265244
+ }
265245
+ i += 1;
265246
+ continue;
265247
+ }
265248
+ i += 1;
265249
+ }
265250
+ return output;
265251
+ }
265252
+ function decodeUtf16LeIgnore(data) {
265253
+ let output = "";
265254
+ let i = 0;
265255
+ while (i + 1 < data.length) {
265256
+ const first = data[i];
265257
+ const second = data[i + 1];
265258
+ if (first === void 0 || second === void 0) break;
265259
+ const codeUnit = first | second << 8;
265260
+ if (codeUnit >= 55296 && codeUnit <= 56319) {
265261
+ const lowFirst = data[i + 2];
265262
+ const lowSecond = data[i + 3];
265263
+ if (lowFirst !== void 0 && lowSecond !== void 0) {
265264
+ const low = lowFirst | lowSecond << 8;
265265
+ if (low >= 56320 && low <= 57343) {
265266
+ const codePoint = 65536 + (codeUnit - 55296 << 10) + (low - 56320);
265267
+ output += String.fromCodePoint(codePoint);
265268
+ i += 4;
265269
+ continue;
265270
+ }
265271
+ }
265272
+ i += 2;
265273
+ continue;
265274
+ }
265275
+ if (codeUnit >= 56320 && codeUnit <= 57343) {
265276
+ i += 2;
265277
+ continue;
265278
+ }
265279
+ output += String.fromCodePoint(codeUnit);
265280
+ i += 2;
265281
+ }
265282
+ return output;
265283
+ }
265284
+ /**
265285
+ * Decode a Buffer into a string with Python-compatible `errors` handling.
265286
+ *
265287
+ * - `'strict'` (default): throw on invalid sequences (via TextDecoder `fatal: true`)
265288
+ * - `'replace'`: substitute each invalid sequence with U+FFFD (TextDecoder default)
265289
+ * - `'ignore'`: drop invalid input sequences while preserving valid U+FFFD characters
265290
+ *
265291
+ * Falls back to `Buffer.toString(encoding)` for encodings TextDecoder does not
265292
+ * support (e.g. `hex`, `base64`, `binary`, `latin1`) — those are lossless
265293
+ * byte-to-character mappings so `errors` has no effect.
265294
+ * @internal
265295
+ */
265296
+ function decodeTextWithErrors(data, encoding, errors = "strict", ignoreBOM = false) {
265297
+ let webLabel;
265298
+ switch (encoding) {
265299
+ case "utf-8":
265300
+ case "utf8":
265301
+ webLabel = "utf-8";
265302
+ break;
265303
+ case "utf16le":
265304
+ case "ucs2":
265305
+ case "ucs-2":
265306
+ webLabel = "utf-16le";
265307
+ break;
265308
+ default: webLabel = void 0;
265309
+ }
265310
+ if (webLabel === void 0) return data.toString(encoding);
265311
+ if (errors === "strict") return new TextDecoder(webLabel, {
265312
+ fatal: true,
265313
+ ignoreBOM
265314
+ }).decode(data);
265315
+ if (errors === "ignore") return webLabel === "utf-8" ? decodeUtf8Ignore(data) : decodeUtf16LeIgnore(data);
265316
+ return new TextDecoder(webLabel, {
265317
+ fatal: false,
265318
+ ignoreBOM
265319
+ }).decode(data);
265320
+ }
265321
+ /**
265322
+ * Convert a glob pattern segment (e.g. "*.txt", "file?.log") into a RegExp.
265323
+ * Mirrors Python pathlib behavior: includes dotfiles, case-sensitive by default.
265324
+ * @internal
265325
+ */
265326
+ function globPatternToRegex(pattern, caseSensitive) {
265327
+ let regex = "^";
265328
+ for (let i = 0; i < pattern.length; i++) {
265329
+ const ch = pattern[i];
265330
+ if (ch === void 0) break;
265331
+ switch (ch) {
265332
+ case "*":
265333
+ regex += "[^/]*";
265334
+ break;
265335
+ case "?":
265336
+ regex += "[^/]";
265337
+ break;
265338
+ case "[": {
265339
+ const end = pattern.indexOf("]", i + 1);
265340
+ if (end === -1) regex += "\\[";
265341
+ else {
265342
+ let charClass = pattern.slice(i + 1, end);
265343
+ charClass = charClass.replace(/\\/g, "\\\\");
265344
+ if (charClass.startsWith("!")) charClass = "^" + charClass.slice(1);
265345
+ else if (charClass.startsWith("^")) charClass = "\\" + charClass;
265346
+ regex += "[" + charClass + "]";
265347
+ i = end;
265348
+ }
265349
+ break;
265350
+ }
265351
+ case "\\":
265352
+ if (i + 1 < pattern.length) {
265353
+ const next = pattern.charAt(i + 1);
265354
+ regex += next.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
265355
+ i++;
265356
+ } else regex += "\\\\";
265357
+ break;
265358
+ default: regex += ch.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
265359
+ }
265360
+ }
265361
+ regex += "$";
265362
+ return new RegExp(regex, caseSensitive ? "" : "i");
265363
+ }
265364
+ var BufferedReadable;
265365
+ var init_internal = __esmMin((() => {
265366
+ BufferedReadable = class extends Readable$1 {
265367
+ _source;
265368
+ _ended = false;
265369
+ constructor(source) {
265370
+ super({ highWaterMark: 128 * 1024 });
265371
+ this._source = source;
265372
+ this._source.on("data", this._onData);
265373
+ this._source.on("end", this._onEnd);
265374
+ this._source.on("close", this._onClose);
265375
+ this._source.on("error", this._onError);
265376
+ }
265377
+ _read() {
265378
+ if (!this._ended && !this.destroyed) this._source.resume();
265379
+ }
265380
+ _destroy(error, callback) {
265381
+ this._source.off("data", this._onData);
265382
+ this._source.off("end", this._onEnd);
265383
+ this._source.off("close", this._onClose);
265384
+ this._source.off("error", this._onError);
265385
+ this._source.destroy();
265386
+ callback(error);
265387
+ }
265388
+ _onData = (chunk) => {
265389
+ if (!this.push(chunk)) this._source.pause();
265390
+ };
265391
+ _onEnd = () => {
265392
+ this._ended = true;
265393
+ this.push(null);
265394
+ };
265395
+ _onClose = () => {
265396
+ if (!this._ended) {
265397
+ this._ended = true;
265398
+ this.push(null);
265399
+ }
265400
+ };
265401
+ _onError = (error) => {
265402
+ this.destroy(error);
265403
+ };
265404
+ };
265405
+ }));
265406
+ //#endregion
265407
+ //#region ../../packages/kaos/src/login-shell-path.ts
265408
+ /**
265409
+ * Login-shell PATH probe — enrich `process.env.PATH` with entries from the
265410
+ * user's login shell.
265411
+ *
265412
+ * When blun is launched from a context that skipped the user's shell
265413
+ * profile (GUI launchers, non-login parent shells), `process.env.PATH`
265414
+ * misses entries like `/opt/homebrew/bin`, so commands spawned by the Bash
265415
+ * tool can't find tools the user has in their interactive shell (e.g.
265416
+ * `gh`). We run the user's login shell once (`$SHELL -l -c /usr/bin/env`),
265417
+ * extract its PATH, and append the entries the current PATH lacks. Existing
265418
+ * entries keep their order and priority; failures (no resolvable shell,
265419
+ * hung or broken profile) silently leave PATH untouched.
265420
+ *
265421
+ * launchd/daemon launches can leave `$SHELL` unset or blank (see
265422
+ * `defaultShell()` in agent-core's terminalService for the same case), so
265423
+ * the probe falls back to the OS account's login shell from the user
265424
+ * database before giving up.
265425
+ *
265426
+ * Like `detectEnvironment`, the probe is a pure function of injected deps
265427
+ * so the suite runs identically on any host. Windows is skipped: the
265428
+ * problem is specific to POSIX login-shell profiles.
265429
+ */
265430
+ /**
265431
+ * Run the user's login shell and return its PATH, or `undefined` when the
265432
+ * probe does not apply (Windows, no resolvable shell) or fails (spawn
265433
+ * error, timeout, no PATH in the output).
265434
+ */
265435
+ async function probeLoginShellPath(deps) {
265436
+ if (deps.platform === "win32") return void 0;
265437
+ const envShell = deps.env["SHELL"]?.trim();
265438
+ const shell = envShell === void 0 || envShell.length === 0 ? deps.userShell() : envShell;
265439
+ if (shell === void 0 || shell.length === 0) return void 0;
265440
+ const stdout = await deps.execFileText(shell, [
265441
+ "-l",
265442
+ "-c",
265443
+ "/usr/bin/env"
265444
+ ], LOGIN_SHELL_ENV_TIMEOUT_MS);
265445
+ if (stdout === void 0) return void 0;
265446
+ let path;
265447
+ for (const line of stdout.split("\n")) if (line.startsWith("PATH=")) path = line.slice(5).trim();
265448
+ if (path === void 0 || path.length === 0) return void 0;
265449
+ return path;
265450
+ }
265451
+ /**
265452
+ * Union of the current PATH and the login-shell PATH: the current PATH
265453
+ * string is kept verbatim — including empty components, which POSIX
265454
+ * command lookup treats as the current directory — and login-shell
265455
+ * entries the current PATH lacks are appended in their own order. When
265456
+ * nothing is missing the current string is returned unchanged. Only
265457
+ * absolute login-shell entries are imported: empty, `.`, and relative
265458
+ * components are all cwd-dependent lookup, and appending one the user
265459
+ * did not already have would widen their search path — LocalKaos runs
265460
+ * commands from arbitrary workspace directories.
265461
+ */
265462
+ function mergeLoginShellPath(currentPath, loginShellPath) {
265463
+ const current = currentPath ?? "";
265464
+ const seen = new Set(current.split(":").filter((entry) => entry.length > 0));
265465
+ const additions = [];
265466
+ for (const entry of loginShellPath.split(":")) {
265467
+ if (!entry.startsWith("/") || seen.has(entry)) continue;
265468
+ seen.add(entry);
265469
+ additions.push(entry);
265470
+ }
265471
+ if (additions.length === 0) return current;
265472
+ if (currentPath === void 0) return additions.join(":");
265473
+ return `${current}:${additions.join(":")}`;
265474
+ }
265475
+ /** Probe the login shell and merge its PATH into `deps.env['PATH']`. */
265476
+ async function applyLoginShellPath(deps) {
265477
+ const loginShellPath = await probeLoginShellPath(deps);
265478
+ if (loginShellPath === void 0) return;
265479
+ const currentPath = deps.env["PATH"];
265480
+ const merged = mergeLoginShellPath(currentPath, loginShellPath);
265481
+ if (merged === (currentPath ?? "")) return;
265482
+ deps.env["PATH"] = merged;
265483
+ }
265484
+ /**
265485
+ * Production convenience — apply the probe to `process.env` once per
265486
+ * process. Memoised like `detectEnvironmentFromNode`: the login-shell PATH
265487
+ * does not change for the lifetime of the process, and repeated
265488
+ * `LocalKaos.create()` calls must not re-spawn the shell.
265489
+ */
265490
+ /**
265491
+ * Login shell from the OS user database (`/etc/passwd` via getpwuid on
265492
+ * Linux, Directory Services on macOS). `userInfo()` throws when the uid
265493
+ * has no database entry (e.g. containers running an arbitrary uid), and
265494
+ * service accounts may carry `/usr/sbin/nologin` — the latter needs no
265495
+ * special casing here because probing it simply fails and degrades
265496
+ * silently.
265497
+ */
265498
+ function userShellFromNode() {
265499
+ try {
265500
+ const shell = userInfo().shell;
265501
+ return shell === null || shell.length === 0 ? void 0 : shell;
265502
+ } catch {
265503
+ return;
265504
+ }
265505
+ }
265506
+ function applyLoginShellPathFromNode() {
265507
+ if (appliedLoginShellPath !== void 0) return appliedLoginShellPath;
265508
+ appliedLoginShellPath = applyLoginShellPath({
265509
+ platform: process.platform,
265510
+ env: process.env,
265511
+ userShell: userShellFromNode,
265512
+ execFileText: execFileText$1
265513
+ });
265514
+ return appliedLoginShellPath;
265515
+ }
265516
+ var LOGIN_SHELL_ENV_TIMEOUT_MS, appliedLoginShellPath;
265517
+ var init_login_shell_path = __esmMin((() => {
265518
+ init_environment$1();
265519
+ LOGIN_SHELL_ENV_TIMEOUT_MS = 5e3;
265520
+ }));
265521
+ //#endregion
265522
+ //#region ../../packages/kaos/src/local.ts
265523
+ /**
265524
+ * Build the `(dev, ino)` cycle-detection key used by `_globWalk`'s
265525
+ * visited set. Returns `null` when `ino` is 0, which Node returns on
265526
+ * filesystems that don't carry inodes (Windows FAT/exFAT, some SMB/NFS
265527
+ * mounts). A null key signals "no reliable identity for this dir" so
265528
+ * the caller skips visited tracking for that descent — cycle safety
265529
+ * is weakened on those filesystems, but normal walking works instead
265530
+ * of every directory colliding on the shared key `"<dev>:0"`.
265531
+ */
265532
+ function cycleKey(s) {
265533
+ if (s.ino === 0) return null;
265534
+ return `${String(s.dev)}:${String(s.ino)}`;
265535
+ }
265536
+ function buildLocalSpawnOptions(_isWindows, cwd, env) {
265537
+ return {
265538
+ cwd,
265539
+ env,
265540
+ stdio: [
265541
+ "pipe",
265542
+ "pipe",
265543
+ "pipe"
265544
+ ],
265545
+ detached: true,
265546
+ windowsHide: true
265547
+ };
265548
+ }
265549
+ function isUtf8Encoding(encoding) {
265550
+ return encoding === "utf-8" || encoding === "utf8";
265551
+ }
265552
+ function* splitLinesKeepingTerminator(text) {
265553
+ if (text.length === 0) return;
265554
+ let start = 0;
265555
+ for (let i = 0; i < text.length; i += 1) if (text.codePointAt(i) === 10) {
265556
+ yield text.slice(start, i + 1);
265557
+ start = i + 1;
265558
+ }
265559
+ if (start < text.length) yield text.slice(start);
265560
+ }
265561
+ function updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr) {
265562
+ for (let i = 0; i < chunk.length; i += 1) {
265563
+ const byte = chunk[i];
265564
+ if (byte === void 0) continue;
265565
+ if (byte === 13) {
265566
+ if (prevWasCr) flags.hasLoneCr = true;
265567
+ prevWasCr = true;
265568
+ } else if (byte === 10) {
265569
+ if (prevWasCr) flags.hasCrLf = true;
265570
+ else flags.hasLf = true;
265571
+ prevWasCr = false;
265572
+ } else {
265573
+ if (prevWasCr) flags.hasLoneCr = true;
265574
+ prevWasCr = false;
265575
+ }
265576
+ }
265577
+ return prevWasCr;
265578
+ }
265579
+ function createUtf8Validator() {
265580
+ let needed = 0;
265581
+ let lower = 128;
265582
+ let upper = 191;
265583
+ const fail = () => {
265584
+ throw new TypeError("Invalid UTF-8 data");
265585
+ };
265586
+ return {
265587
+ write(chunk) {
265588
+ for (let i = 0; i < chunk.length; i += 1) {
265589
+ const byte = chunk[i];
265590
+ if (byte === void 0) continue;
265591
+ if (needed === 0) {
265592
+ if (byte <= 127) continue;
265593
+ if (byte >= 194 && byte <= 223) needed = 1;
265594
+ else if (byte === 224) {
265595
+ needed = 2;
265596
+ lower = 160;
265597
+ } else if (byte >= 225 && byte <= 236) needed = 2;
265598
+ else if (byte === 237) {
265599
+ needed = 2;
265600
+ upper = 159;
265601
+ } else if (byte >= 238 && byte <= 239) needed = 2;
265602
+ else if (byte === 240) {
265603
+ needed = 3;
265604
+ lower = 144;
265605
+ } else if (byte >= 241 && byte <= 243) needed = 3;
265606
+ else if (byte === 244) {
265607
+ needed = 3;
265608
+ upper = 143;
265609
+ } else fail();
265610
+ } else {
265611
+ if (byte < lower || byte > upper) fail();
265612
+ lower = 128;
265613
+ upper = 191;
265614
+ needed -= 1;
265615
+ }
265616
+ }
265617
+ },
265618
+ end() {
265619
+ if (needed !== 0) fail();
265620
+ }
265621
+ };
265622
+ }
265623
+ async function readRange(fh, start, length) {
265624
+ const data = Buffer.alloc(length);
265625
+ let offset = 0;
265626
+ while (offset < length) {
265627
+ const { bytesRead } = await fh.read(data, offset, length - offset, start + offset);
265628
+ if (bytesRead === 0) break;
265629
+ offset += bytesRead;
265630
+ }
265631
+ return offset === length ? data : data.subarray(0, offset);
265632
+ }
265633
+ function waitForSpawn(child) {
265634
+ return new Promise((resolve, reject) => {
265635
+ const onSpawn = () => {
265636
+ child.off("error", onError);
265637
+ resolve();
265638
+ };
265639
+ const onError = (err) => {
265640
+ child.off("spawn", onSpawn);
265641
+ reject(err);
265642
+ };
265643
+ child.once("spawn", onSpawn);
265644
+ child.once("error", onError);
265645
+ });
265646
+ }
265647
+ var isWindows$1, READ_CHUNK_SIZE, LocalProcess, LocalKaos;
265648
+ var init_local = __esmMin((() => {
265649
+ init_dist$6();
265650
+ init_environment$1();
265651
+ init_errors$1();
265652
+ init_internal();
265653
+ init_login_shell_path();
265654
+ isWindows$1 = process.platform === "win32";
265655
+ READ_CHUNK_SIZE = 64 * 1024;
265656
+ LocalProcess = class {
265657
+ stdin;
265658
+ stdout;
265659
+ stderr;
265660
+ pid;
265661
+ _child;
265662
+ _exitCode = null;
265663
+ _exitPromise;
265664
+ _disposed = false;
265665
+ constructor(child) {
265666
+ if (child.stdin === null || child.stdout === null || child.stderr === null) throw new Error("Process must be created with stdin/stdout/stderr pipes.");
265667
+ this._child = child;
265668
+ this.stdin = child.stdin;
265669
+ this.stdout = new BufferedReadable(child.stdout);
265670
+ this.stderr = new BufferedReadable(child.stderr);
265671
+ this.pid = child.pid ?? -1;
265672
+ this._exitPromise = new Promise((resolve, reject) => {
265673
+ child.on("exit", (code) => {
265674
+ this._exitCode = code ?? -1;
265675
+ resolve(this._exitCode);
265676
+ });
265677
+ child.on("error", (error) => {
265678
+ reject(error);
265679
+ });
265680
+ });
265681
+ }
265682
+ get exitCode() {
265683
+ return this._exitCode;
265684
+ }
265685
+ async wait() {
265686
+ return this._exitPromise;
265687
+ }
265688
+ kill(signal) {
265689
+ if (this.pid <= 0) return Promise.resolve();
265690
+ if (isWindows$1) {
265691
+ const taskkillArgs = [
265692
+ "/T",
265693
+ "/F",
265694
+ "/PID",
265695
+ String(this.pid)
265696
+ ];
265697
+ return new Promise((resolve) => {
265698
+ const killer = spawn("taskkill", taskkillArgs, {
265699
+ stdio: "ignore",
265700
+ windowsHide: true
265701
+ });
265702
+ const done = () => {
265703
+ resolve();
265704
+ };
265705
+ killer.once("error", done);
265706
+ killer.once("close", done);
265707
+ });
265708
+ }
265709
+ try {
265710
+ process.kill(-this.pid, signal ?? "SIGTERM");
265711
+ } catch (error) {
265712
+ const err = error;
265713
+ if (err.code === "ESRCH") return Promise.resolve();
265714
+ if (err.code === "EPERM") {
265715
+ try {
265716
+ this._child.kill(signal ?? "SIGTERM");
265717
+ } catch {}
265718
+ return Promise.resolve();
265719
+ }
265720
+ throw error;
265721
+ }
265722
+ return Promise.resolve();
265723
+ }
265724
+ dispose() {
265725
+ if (this._disposed) return;
265726
+ this._disposed = true;
265727
+ this.stdin.destroy();
265728
+ this.stdout.destroy();
265729
+ this.stderr.destroy();
265730
+ }
265731
+ };
265732
+ LocalKaos = class LocalKaos {
265733
+ name = "local";
265734
+ osEnv;
265735
+ _cwd;
265736
+ _envLayers;
265737
+ constructor(osEnv, cwd, envLayers = []) {
265738
+ this._cwd = normalize$4(cwd ?? process.cwd());
265739
+ this.osEnv = osEnv;
265740
+ this._envLayers = envLayers;
265741
+ }
265742
+ /**
265743
+ * Construct a fresh `LocalKaos` after probing the host environment.
265744
+ *
265745
+ * Each call returns a new instance with its own `_cwd`; concurrent
265746
+ * callers can therefore operate on independent working directories
265747
+ * without polluting one another.
265748
+ */
265749
+ static async create() {
265750
+ const [osEnv] = await Promise.all([detectEnvironmentFromNode(), applyLoginShellPathFromNode()]);
265751
+ return new LocalKaos(osEnv);
265752
+ }
265753
+ withCwd(cwd) {
265754
+ return new LocalKaos(this.osEnv, cwd, this._envLayers);
265755
+ }
265756
+ withEnv(env) {
265757
+ return new LocalKaos(this.osEnv, this._cwd, [...this._envLayers, env]);
265758
+ }
265759
+ _resolvePath(path) {
265760
+ if (isAbsolute$2(path)) return normalize$4(path);
265761
+ return join$4(this._cwd, path);
265762
+ }
265763
+ pathClass() {
265764
+ return isWindows$1 ? "win32" : "posix";
265765
+ }
265766
+ normpath(path) {
265767
+ return normalize$4(path);
265768
+ }
265769
+ gethome() {
265770
+ return normalize$4(homedir());
265771
+ }
265772
+ getcwd() {
265773
+ return this._cwd;
265774
+ }
265775
+ /**
265776
+ * Change the working directory of this LocalKaos instance.
265777
+ *
265778
+ * Unlike Python's `os.chdir`, this is instance-scoped and never touches
265779
+ * `process.cwd()`. Child processes spawned via {@link exec} inherit this
265780
+ * instance's `_cwd`; concurrent LocalKaos instances each carry their own
265781
+ * independent cwd. If you need Python-compatible process-global cwd,
265782
+ * call `process.chdir(x)` directly.
265783
+ */
265784
+ async chdir(path) {
265785
+ const resolved = this._resolvePath(path);
265786
+ if (!(await stat(resolved)).isDirectory()) throw new Error(`Not a directory: ${resolved}`);
265787
+ this._cwd = resolved;
265788
+ }
265789
+ async stat(path, options) {
265790
+ const resolved = this._resolvePath(path);
265791
+ const s = options?.followSymlinks ?? true ? await stat(resolved) : await lstat(resolved);
265792
+ return {
265793
+ stMode: s.mode,
265794
+ stIno: s.ino,
265795
+ stDev: s.dev,
265796
+ stNlink: s.nlink,
265797
+ stUid: s.uid,
265798
+ stGid: s.gid,
265799
+ stSize: s.size,
265800
+ stAtime: s.atimeMs / 1e3,
265801
+ stMtime: s.mtimeMs / 1e3,
265802
+ stCtime: isWindows$1 ? s.birthtimeMs / 1e3 : s.ctimeMs / 1e3
265803
+ };
265804
+ }
265805
+ async *iterdir(path) {
265806
+ const resolved = this._resolvePath(path);
265807
+ const entries = await readdir(resolved);
265808
+ for (const entry of entries) yield join$4(resolved, entry);
265809
+ }
265810
+ async *glob(path, pattern, options) {
265811
+ const resolved = this._resolvePath(path);
265812
+ const caseSensitive = options?.caseSensitive ?? true;
265813
+ const patternParts = pattern.split("/");
265814
+ const initVisited = /* @__PURE__ */ new Set();
265815
+ try {
265816
+ const rootKey = cycleKey(await stat(resolved));
265817
+ if (rootKey !== null) initVisited.add(rootKey);
265818
+ } catch {}
265819
+ yield* this._globWalk(resolved, patternParts, caseSensitive, initVisited);
265820
+ }
265821
+ async *_globWalk(basePath, patternParts, caseSensitive, visited) {
265822
+ if (patternParts.length === 0) return;
265823
+ const [currentPattern, ...remainingParts] = patternParts;
265824
+ if (currentPattern === "**") {
265825
+ if (remainingParts.length > 0) yield* this._globWalk(basePath, remainingParts, caseSensitive, visited);
265826
+ else yield basePath;
265827
+ let entries;
265828
+ try {
265829
+ entries = await readdir(basePath);
265830
+ } catch {
265831
+ return;
265832
+ }
265833
+ for (const entry of entries) {
265834
+ const fullPath = join$4(basePath, entry);
265835
+ let entryStat;
265836
+ try {
265837
+ entryStat = await stat(fullPath);
265838
+ } catch {
265839
+ continue;
265840
+ }
265841
+ if (entryStat.isDirectory()) {
265842
+ const key = cycleKey(entryStat);
265843
+ if (key !== null && visited.has(key)) continue;
265844
+ yield* this._globWalk(fullPath, patternParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
265845
+ } else if (remainingParts.length === 0) yield fullPath;
265846
+ }
265847
+ } else {
265848
+ const regex = globPatternToRegex(currentPattern ?? "", caseSensitive);
265849
+ let entries;
265850
+ try {
265851
+ entries = await readdir(basePath);
265852
+ } catch {
265853
+ return;
265854
+ }
265855
+ for (const entry of entries) {
265856
+ if (!regex.test(entry)) continue;
265857
+ const fullPath = join$4(basePath, entry);
265858
+ if (remainingParts.length === 0) yield fullPath;
265859
+ else {
265860
+ let entryStat;
265861
+ try {
265862
+ entryStat = await stat(fullPath);
265863
+ } catch {
265864
+ continue;
265865
+ }
265866
+ if (entryStat.isDirectory()) {
265867
+ const key = cycleKey(entryStat);
265868
+ if (key !== null && visited.has(key)) continue;
265869
+ yield* this._globWalk(fullPath, remainingParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
265870
+ }
265871
+ }
265872
+ }
265873
+ }
265874
+ }
265875
+ async readBytes(path, n) {
265876
+ const resolved = this._resolvePath(path);
265877
+ if (n === void 0) return Buffer.from(await readFile(resolved));
265878
+ const fh = await open(resolved, "r");
265879
+ try {
265880
+ const buf = Buffer.alloc(n);
265881
+ const { bytesRead } = await fh.read(buf, 0, n, 0);
265882
+ return buf.subarray(0, bytesRead);
265883
+ } finally {
265884
+ await fh.close();
265885
+ }
265886
+ }
265887
+ async readText(path, options) {
265888
+ const resolved = this._resolvePath(path);
265889
+ const encoding = options?.encoding ?? "utf-8";
265890
+ const errors = options?.errors ?? "strict";
265891
+ return decodeTextWithErrors(await readFile(resolved), encoding, errors);
265892
+ }
265893
+ async *readLines(path, options) {
265894
+ const resolved = this._resolvePath(path);
265895
+ const encoding = options?.encoding ?? "utf-8";
265896
+ const errors = options?.errors ?? "strict";
265897
+ if (!isUtf8Encoding(encoding)) {
265898
+ yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readFile(resolved), encoding, errors));
265899
+ return;
265900
+ }
265901
+ yield* this._readUtf8Lines(resolved, errors);
265902
+ }
265903
+ async scanTextFile(path) {
265904
+ const fh = await open(this._resolvePath(path), "r");
265905
+ try {
265906
+ const buf = Buffer.alloc(READ_CHUNK_SIZE);
265907
+ const flags = {
265908
+ hasCrLf: false,
265909
+ hasLf: false,
265910
+ hasLoneCr: false
265911
+ };
265912
+ const validator = createUtf8Validator();
265913
+ let totalLines = 0;
265914
+ let totalBytes = 0;
265915
+ let endsWithNewline = false;
265916
+ let hasNul = false;
265917
+ let prevWasCr = false;
265918
+ while (true) {
265919
+ const { bytesRead } = await fh.read(buf, 0, buf.length, null);
265920
+ if (bytesRead === 0) break;
265921
+ const chunk = buf.subarray(0, bytesRead);
265922
+ validator.write(chunk);
265923
+ for (let i = 0; i < chunk.length; i += 1) {
265924
+ const byte = chunk[i];
265925
+ if (byte === void 0) continue;
265926
+ if (byte === 0) hasNul = true;
265927
+ if (byte === 10) totalLines += 1;
265928
+ }
265929
+ prevWasCr = updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr);
265930
+ totalBytes += bytesRead;
265931
+ endsWithNewline = chunk[bytesRead - 1] === 10;
265932
+ }
265933
+ if (prevWasCr) flags.hasLoneCr = true;
265934
+ validator.end();
265935
+ if (totalBytes > 0 && !endsWithNewline) totalLines += 1;
265936
+ return {
265937
+ totalLines,
265938
+ endsWithNewline,
265939
+ hasNul,
265940
+ lineEndingFlags: flags
265941
+ };
265942
+ } finally {
265943
+ await fh.close();
265944
+ }
265945
+ }
265946
+ async *readLineRange(path, options) {
265947
+ const resolved = this._resolvePath(path);
265948
+ const errors = options.errors ?? "strict";
265949
+ yield* this._readUtf8Lines(resolved, errors, {
265950
+ startLine: options.startLine,
265951
+ maxLines: options.maxLines
265952
+ });
265953
+ }
265954
+ async *readTailLines(path, options) {
265955
+ if (options.tailCount <= 0) return;
265956
+ const resolved = this._resolvePath(path);
265957
+ const errors = options.errors ?? "strict";
265958
+ const fh = await open(resolved, "r");
265959
+ try {
265960
+ const s = await fh.stat();
265961
+ if (s.size === 0) return;
265962
+ let pos = s.size;
265963
+ let foundLf = 0;
265964
+ let startOffset = 0;
265965
+ let needLf = options.tailCount;
265966
+ let sawTailBlock = false;
265967
+ while (pos > 0 && foundLf < needLf) {
265968
+ const readSize = Math.min(READ_CHUNK_SIZE, pos);
265969
+ pos -= readSize;
265970
+ const buf = Buffer.alloc(readSize);
265971
+ await fh.read(buf, 0, readSize, pos);
265972
+ if (!sawTailBlock) {
265973
+ sawTailBlock = true;
265974
+ needLf = buf[readSize - 1] === 10 ? options.tailCount + 1 : options.tailCount;
265975
+ }
265976
+ for (let i = readSize - 1; i >= 0; i -= 1) {
265977
+ if (buf[i] !== 10) continue;
265978
+ foundLf += 1;
265979
+ if (foundLf === needLf) {
265980
+ startOffset = pos + i + 1;
265981
+ break;
265982
+ }
265983
+ }
265984
+ }
265985
+ if (foundLf < needLf) startOffset = 0;
265986
+ yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readRange(fh, startOffset, s.size - startOffset), "utf-8", errors, startOffset !== 0));
265987
+ } finally {
265988
+ await fh.close();
265989
+ }
265990
+ }
265991
+ async *_readUtf8Lines(resolved, errors, range) {
265992
+ const startLine = range?.startLine ?? 1;
265993
+ const maxLines = range?.maxLines ?? Number.POSITIVE_INFINITY;
265994
+ const fh = await open(resolved, "r");
265995
+ try {
265996
+ const buf = Buffer.alloc(READ_CHUNK_SIZE);
265997
+ let pending = [];
265998
+ let pendingOffset = 0;
265999
+ let fileOffset = 0;
266000
+ let lineNo = 1;
266001
+ let yielded = 0;
266002
+ while (true) {
266003
+ const { bytesRead } = await fh.read(buf, 0, buf.length, null);
266004
+ if (bytesRead === 0) break;
266005
+ const chunk = buf.subarray(0, bytesRead);
266006
+ let lineStart = 0;
266007
+ for (let i = 0; i < chunk.length; i += 1) {
266008
+ if (chunk[i] !== 10) continue;
266009
+ const piece = chunk.subarray(lineStart, i + 1);
266010
+ const lineOffset = pending.length === 0 ? fileOffset + lineStart : pendingOffset;
266011
+ const line = pending.length === 0 ? piece : Buffer.concat([...pending, piece]);
266012
+ if (lineNo >= startLine) {
266013
+ yield decodeTextWithErrors(line, "utf-8", errors, lineOffset !== 0);
266014
+ yielded += 1;
266015
+ if (yielded >= maxLines) return;
266016
+ }
266017
+ pending = [];
266018
+ lineStart = i + 1;
266019
+ lineNo += 1;
266020
+ }
266021
+ if (lineStart < chunk.length) {
266022
+ const tail = Buffer.from(chunk.subarray(lineStart));
266023
+ if (pending.length === 0) pendingOffset = fileOffset + lineStart;
266024
+ pending.push(tail);
266025
+ }
266026
+ fileOffset += bytesRead;
266027
+ }
266028
+ if (pending.length > 0) {
266029
+ const line = Buffer.concat(pending);
266030
+ if (lineNo >= startLine) yield decodeTextWithErrors(line, "utf-8", errors, pendingOffset !== 0);
266031
+ }
266032
+ } finally {
266033
+ await fh.close();
266034
+ }
266035
+ }
266036
+ async writeBytes(path, data) {
266037
+ await writeFile(this._resolvePath(path), data);
266038
+ return data.length;
266039
+ }
266040
+ async writeText(path, data, options) {
266041
+ const resolved = this._resolvePath(path);
266042
+ const encoding = options?.encoding ?? "utf-8";
266043
+ if ((options?.mode ?? "w") === "a") await appendFile(resolved, data, encoding);
266044
+ else await writeFile(resolved, data, encoding);
266045
+ return data.length;
266046
+ }
266047
+ async mkdir(path, options) {
266048
+ const resolved = this._resolvePath(path);
266049
+ const parents = options?.parents ?? false;
266050
+ const existOk = options?.existOk ?? false;
266051
+ if (parents) {
266052
+ if (!existOk) try {
266053
+ if ((await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists`);
266054
+ } catch (error) {
266055
+ if (error instanceof KaosFileExistsError) throw error;
266056
+ if (error.code !== "ENOENT") throw error;
266057
+ }
266058
+ await mkdir(resolved, { recursive: true });
266059
+ return;
266060
+ }
266061
+ try {
266062
+ await mkdir(resolved);
266063
+ } catch (error) {
266064
+ if (existOk && error instanceof Error && "code" in error && error.code === "EEXIST") {
266065
+ if (!(await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists but is not a directory`);
266066
+ return;
266067
+ }
266068
+ throw error;
266069
+ }
266070
+ }
266071
+ async exec(...args) {
266072
+ const command = args[0];
266073
+ if (command === void 0) throw new Error("LocalKaos.exec(): at least one argument (the command to run) is required.");
266074
+ const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv()));
266075
+ await waitForSpawn(child);
266076
+ return new LocalProcess(child);
266077
+ }
266078
+ async execWithEnv(args, env) {
266079
+ const command = args[0];
266080
+ if (command === void 0) throw new Error("LocalKaos.execWithEnv(): at least one argument (the command to run) is required.");
266081
+ const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv(env)));
266082
+ await waitForSpawn(child);
266083
+ return new LocalProcess(child);
266084
+ }
266085
+ _buildExecEnv(invocationEnv) {
266086
+ if (this._envLayers.length === 0) return invocationEnv;
266087
+ const merged = {
266088
+ ...process.env,
266089
+ ...invocationEnv
266090
+ };
266091
+ for (const layer of this._envLayers) Object.assign(merged, layer);
266092
+ return merged;
266093
+ }
266094
+ };
266095
+ }));
266096
+ var init_current = __esmMin((() => {
266097
+ new AsyncLocalStorage();
266098
+ }));
266099
+ //#endregion
266100
+ //#region ../../packages/kaos/src/index.ts
266101
+ var init_src$1 = __esmMin((() => {
266102
+ init_environment$1();
266103
+ init_errors$1();
266104
+ init_local();
266105
+ init_current();
266106
+ }));
266107
+ //#endregion
266108
+ //#region ../../packages/agent-core/src/agent/turn/historical-tool-projection.ts
266109
+ function identity(stat) {
266110
+ return `${stat.dev}:${stat.ino}:${stat.birthtimeNs}`;
266111
+ }
266112
+ function revision(stat) {
266113
+ return `${identity(stat)}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}:${stat.mode}`;
266114
+ }
266115
+ function canonical(path) {
266116
+ const normalized = resolve(path);
266117
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
266118
+ }
266119
+ async function checkedDirectory(path) {
266120
+ const stat = await lstat(path, { bigint: true });
266121
+ if (!stat.isDirectory() || stat.isSymbolicLink() || canonical(await realpath(path)) !== canonical(path)) throw new Error("Unsafe recovery directory");
266122
+ return stat;
266123
+ }
266124
+ async function checkedAncestors(path) {
266125
+ for (let current = resolve(path); current !== parse(current).root; current = dirname(current)) await checkedDirectory(current);
266126
+ }
266127
+ function persistableText(message) {
266128
+ if (message.isError === true || message.content.length !== 1) return void 0;
266129
+ const part = message.content[0];
266130
+ if (part?.type !== "text") return void 0;
266131
+ const text = part.text;
266132
+ if (text.length < MIN_CHARS || text.includes("\0") || RECOVERY_REFERENCE.test(text) || Buffer.from(text, "utf8").toString("utf8") !== text || text.split("\n").some((line) => line.length > 2e3)) return void 0;
266133
+ return text;
266134
+ }
266135
+ var MIN_CHARS, MAX_REFERENCE_CHARS, RECOVERY_REFERENCE, HistoricalToolProjection;
266136
+ var init_historical_tool_projection = __esmMin((() => {
266137
+ init_src$1();
266138
+ init_src$3();
266139
+ init_read();
266140
+ init_file_type$1();
266141
+ init_fs$3();
266142
+ MIN_CHARS = 3e3;
266143
+ MAX_REFERENCE_CHARS = 2500;
266144
+ RECOVERY_REFERENCE = /\[Tool result offloaded\]|\[Repeated tool result omitted\]|^output_path\s*:|Tool output exceeded \d+ characters/m;
266145
+ HistoricalToolProjection = class {
266146
+ agent;
266147
+ files = /* @__PURE__ */ new Map();
266148
+ directories = /* @__PURE__ */ new Map();
266149
+ dirtyDirectories = /* @__PURE__ */ new Set();
266150
+ pending = Promise.resolve();
266151
+ constructor(agent) {
266152
+ this.agent = agent;
266153
+ }
266154
+ async project(history, turnAnchor, tools, context) {
266155
+ const unchanged = {
266156
+ history,
266157
+ changed: false
266158
+ };
266159
+ const boundary = turnAnchor === void 0 ? -1 : history.indexOf(turnAnchor);
266160
+ const read = tools.find((tool) => tool.name === "Read");
266161
+ if (boundary < 0 || !(read instanceof ReadTool) || !(this.agent.kaos instanceof LocalKaos) || this.agent.homedir === void 0 || this.agent.hooks?.hasMatchingHook("PreToolUse", "Read")) return unchanged;
266162
+ const calls = /* @__PURE__ */ new Map();
266163
+ const results = /* @__PURE__ */ new Map();
266164
+ const knownTools = new Set(this.agent.tools.loopTools.map((tool) => tool.name));
266165
+ history.forEach((message, index) => {
266166
+ for (const call of message.toolCalls) {
266167
+ const previous = calls.get(call.id);
266168
+ calls.set(call.id, {
266169
+ index,
266170
+ name: call.name,
266171
+ count: (previous?.count ?? 0) + 1
266172
+ });
266173
+ }
266174
+ if (message.role === "tool" && message.toolCallId !== void 0) {
266175
+ const previous = results.get(message.toolCallId);
266176
+ results.set(message.toolCallId, {
266177
+ index,
266178
+ count: (previous?.count ?? 0) + 1
266179
+ });
266180
+ }
266181
+ });
266182
+ const closedBatches = /* @__PURE__ */ new Set();
266183
+ for (let index = 0; index < boundary; index++) {
266184
+ const message = history[index];
266185
+ if (message.role === "assistant" && message.toolCalls.length > 0 && message.toolCalls.every((call) => {
266186
+ const result = results.get(call.id);
266187
+ return calls.get(call.id)?.count === 1 && result?.count === 1 && result.index > index && result.index < boundary;
266188
+ })) closedBatches.add(index);
266189
+ }
266190
+ const projected = [...history];
266191
+ let changed = false;
266192
+ for (let index = 0; index < boundary; index++) {
266193
+ context.signal.throwIfAborted();
266194
+ const message = history[index];
266195
+ if (message.role !== "tool" || !message.toolCallId) continue;
266196
+ const call = calls.get(message.toolCallId);
266197
+ if (call === void 0 || call.count !== 1 || !closedBatches.has(call.index) || !knownTools.has(call.name)) continue;
266198
+ const text = persistableText(message);
266199
+ if (text === void 0) continue;
266200
+ const digest = createHash("sha256").update(JSON.stringify([
266201
+ canonical(this.agent.homedir),
266202
+ call.name,
266203
+ message.toolCallId,
266204
+ text
266205
+ ])).digest("hex");
266206
+ const outputPath = join(resolve(this.agent.homedir), "tool-results", "historical", `${digest}.txt`);
266207
+ const header = `[Tool result offloaded]\noutput_path: ${outputPath}\noutput_size_chars: ${text.length}\nsha256: ${createHash("sha256").update(text).digest("hex")}\nnext_step: Use Read with this path and line_offset/n_lines to recover the complete output.
266208
+
266209
+ [preview]
266210
+ `;
266211
+ if (header.length >= MAX_REFERENCE_CHARS) continue;
266212
+ try {
266213
+ if (!await this.canRead(read, outputPath, context)) continue;
266214
+ const save = this.pending.then(() => this.persist(outputPath, text));
266215
+ this.pending = save.catch(() => void 0);
266216
+ await save;
266217
+ const stat = await this.agent.kaos.stat(outputPath);
266218
+ if ((stat.stMode & 61440) !== 32768 || stat.stSize !== Buffer.byteLength(text)) continue;
266219
+ const bytes = await this.agent.kaos.readBytes(outputPath, Buffer.byteLength(text) + 1);
266220
+ if (!Buffer.from(bytes).equals(Buffer.from(text)) || detectFileType(outputPath, Buffer.from(bytes).subarray(0, 512)).kind !== "text") continue;
266221
+ await this.verifyFile(outputPath, text);
266222
+ context.signal.throwIfAborted();
266223
+ if (!await this.canRead(read, outputPath, context)) continue;
266224
+ let preview = text.slice(0, MAX_REFERENCE_CHARS - header.length);
266225
+ if (/[\uD800-\uDBFF]$/.test(preview)) preview = preview.slice(0, -1);
266226
+ projected[index] = {
266227
+ ...message,
266228
+ content: [{
266229
+ type: "text",
266230
+ text: header + preview
266231
+ }]
266232
+ };
266233
+ changed = true;
266234
+ } catch {
266235
+ context.signal.throwIfAborted();
266236
+ }
266237
+ }
266238
+ try {
266239
+ for (const path of this.dirtyDirectories) await this.verifyDirectory(path);
266240
+ this.dirtyDirectories.clear();
266241
+ } catch {
266242
+ return unchanged;
266243
+ }
266244
+ return {
266245
+ history: changed ? projected : history,
266246
+ changed
266247
+ };
266248
+ }
266249
+ async canRead(read, path, context) {
266250
+ if (this.agent.tools.loopTools.find((tool) => tool.name === "Read") !== read || this.agent.hooks?.hasMatchingHook("PreToolUse", "Read")) return false;
266251
+ const args = {
266252
+ path,
266253
+ line_offset: 1,
266254
+ n_lines: 1e3
266255
+ };
266256
+ const execution = read.resolveExecution(args);
266257
+ if ("isError" in execution) return false;
266258
+ const toolCall = {
266259
+ type: "function",
266260
+ id: "historical-recovery-check",
266261
+ name: "Read",
266262
+ arguments: JSON.stringify(args)
266263
+ };
266264
+ for (const policy of this.agent.permission.policies) {
266265
+ const result = await policy.evaluate({
266266
+ ...context,
266267
+ tool: read,
266268
+ toolCall,
266269
+ toolCalls: [toolCall],
266270
+ args,
266271
+ execution
266272
+ });
266273
+ if (result !== void 0) return result.kind === "approve" && result.executionMetadata === void 0;
266274
+ }
266275
+ return false;
266276
+ }
266277
+ async ensureDirectory(path) {
266278
+ let created = false;
266279
+ try {
266280
+ await mkdir(path, { mode: 448 });
266281
+ created = true;
266282
+ } catch (error) {
266283
+ if (error.code !== "EEXIST") throw error;
266284
+ }
266285
+ if (created && this.directories.has(dirname(path))) this.dirtyDirectories.add(dirname(path));
266286
+ const stat = await checkedDirectory(path);
266287
+ const previous = this.directories.get(path);
266288
+ if (previous !== void 0 && previous.identity !== identity(stat)) throw new Error("Recovery directory replaced");
266289
+ if (previous === void 0) {
266290
+ if (created) await hardenPrivatePath(path, "directory");
266291
+ await this.verifyDirectory(path);
266292
+ } else if (previous.revision !== revision(stat) && !this.dirtyDirectories.has(path)) await this.verifyDirectory(path);
266293
+ }
266294
+ async verifyDirectory(path) {
266295
+ const before = await checkedDirectory(path);
266296
+ const previous = this.directories.get(path);
266297
+ if (previous !== void 0 && previous.identity !== identity(before)) throw new Error("Recovery directory replaced");
266298
+ if (process.platform === "win32") new WindowsPrivateAcl().verify(path, "directory");
266299
+ else if ((before.mode & 63n) !== 0n) throw new Error("Recovery directory is not private");
266300
+ const after = await checkedDirectory(path);
266301
+ if (revision(before) !== revision(after)) throw new Error("Recovery directory changed during verification");
266302
+ this.directories.set(path, {
266303
+ identity: identity(after),
266304
+ revision: revision(after)
266305
+ });
266306
+ }
266307
+ async persist(path, text) {
266308
+ await checkedAncestors(dirname(dirname(dirname(path))));
266309
+ await this.ensureDirectory(dirname(dirname(path)));
266310
+ await this.ensureDirectory(dirname(path));
266311
+ if (this.files.has(path)) {
266312
+ await this.verifyFile(path, text);
266313
+ return;
266314
+ }
266315
+ try {
266316
+ await lstat(path);
266317
+ await this.verifyFile(path, text);
266318
+ if (process.platform === "win32") new WindowsPrivateAcl().verify(path, "file");
266319
+ this.files.set(path, revision(await lstat(path, { bigint: true })));
266320
+ return;
266321
+ } catch (error) {
266322
+ if (error.code !== "ENOENT") throw error;
266323
+ }
266324
+ const staging = join(dirname(path), `${randomUUID()}.staging`);
266325
+ this.dirtyDirectories.add(dirname(path));
266326
+ try {
266327
+ await atomicWritePrivate$1(staging, text);
266328
+ await checkedAncestors(dirname(path));
266329
+ await link(staging, path);
266330
+ } finally {
266331
+ await unlink(staging).catch(() => void 0);
266332
+ }
266333
+ await this.verifyFile(path, text);
266334
+ this.files.set(path, revision(await lstat(path, { bigint: true })));
266335
+ }
266336
+ async verifyFile(path, text) {
266337
+ const before = await lstat(path, { bigint: true });
266338
+ if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.size !== BigInt(Buffer.byteLength(text)) || process.platform !== "win32" && (before.mode & 63n) !== 0n || canonical(await realpath(path)) !== canonical(path)) throw new Error("Unsafe recovery artifact");
266339
+ const previous = this.files.get(path);
266340
+ if (previous !== void 0 && previous !== revision(before)) throw new Error("Recovery artifact changed");
266341
+ const handle = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
266342
+ try {
266343
+ if (revision(await handle.stat({ bigint: true })) !== revision(before) || !(await handle.readFile()).equals(Buffer.from(text)) || revision(await handle.stat({ bigint: true })) !== revision(before) || revision(await lstat(path, { bigint: true })) !== revision(before) || canonical(await realpath(path)) !== canonical(path)) throw new Error("Recovery artifact changed");
266344
+ } finally {
266345
+ await handle.close();
266346
+ }
266347
+ }
266348
+ };
266349
+ }));
266350
+ //#endregion
264970
266351
  //#region ../../packages/agent-core/src/agent/response-review-decision.ts
264971
266352
  /** Never carry provider/guard prose, signed receipts or arbitrary codes into logs. */
264972
266353
  function rejectionFindings(report) {
@@ -265611,6 +266992,7 @@ var init_turn = __esmMin((() => {
265611
266992
  init_canonical_args();
265612
266993
  init_tool_dedup();
265613
266994
  init_tool_result_budget();
266995
+ init_historical_tool_projection();
265614
266996
  init_response_review();
265615
266997
  import_turn_tool_performance_policy = require_turn_tool_performance_policy();
265616
266998
  BLUN_LEAN_TOOL_NAMES = new Set([
@@ -265693,8 +267075,10 @@ var init_turn = __esmMin((() => {
265693
267075
  stepFailureByTurn = /* @__PURE__ */ new Map();
265694
267076
  providerRequestFailureByTurn = /* @__PURE__ */ new Map();
265695
267077
  currentStep = 0;
267078
+ historicalToolProjection;
265696
267079
  constructor(agent) {
265697
267080
  this.agent = agent;
267081
+ this.historicalToolProjection = new HistoricalToolProjection(agent);
265698
267082
  }
265699
267083
  /** Explicit host shutdown only; ordinary cancellation remains resumable. */
265700
267084
  closeAdmission() {
@@ -266100,7 +267484,10 @@ var init_turn = __esmMin((() => {
266100
267484
  turnId,
266101
267485
  origin
266102
267486
  });
267487
+ const previousLastMessage = this.agent.context.history.at(-1);
266103
267488
  this.agent.context.appendUserMessage(input, origin);
267489
+ const appendedMessage = this.agent.context.history.at(-1);
267490
+ const turnAnchor = appendedMessage !== previousLastMessage && appendedMessage?.role === "user" && appendedMessage.origin === origin ? appendedMessage : void 0;
266104
267491
  const startedAt = Date.now();
266105
267492
  let ended;
266106
267493
  let blockedByUserPromptHook = false;
@@ -266127,7 +267514,7 @@ var init_turn = __esmMin((() => {
266127
267514
  this.agent.log.warn("Turn-start observation failed");
266128
267515
  }
266129
267516
  signal.throwIfAborted();
266130
- const stopReason = await this.runStepLoop(turnId, signal, input, origin, (response) => {
267517
+ const stopReason = await this.runStepLoop(turnId, signal, input, origin, turnAnchor, (response) => {
266131
267518
  completedResponse = response;
266132
267519
  });
266133
267520
  completedStopReason = stopReason;
@@ -266292,7 +267679,7 @@ var init_turn = __esmMin((() => {
266292
267679
  content: hookResult.message
266293
267680
  });
266294
267681
  }
266295
- async runStepLoop(turnId, signal, input, origin, onFinalResponse) {
267682
+ async runStepLoop(turnId, signal, input, origin, turnAnchor, onFinalResponse) {
266296
267683
  let stopHookContinuationUsed = false;
266297
267684
  let goalOutcomeMessageContinuationUsed = false;
266298
267685
  const deduper = new ToolCallDeduplicator({ telemetry: this.agent.telemetry });
@@ -266305,11 +267692,25 @@ var init_turn = __esmMin((() => {
266305
267692
  let taskToolsNeeded = turnNeedsThinking || blunTurnHasAttachment(input);
266306
267693
  const greetingAnchor = this.agent.context.history.findLast((message) => message.role === "user" && message.origin?.kind === "user");
266307
267694
  let partialContext = false;
266308
- const turnHistory = () => {
266309
- const history = personalMemoryRecall === void 0 ? this.agent.context.history : [...this.agent.context.history, personalMemoryRecall];
267695
+ const turnHistory = async (tools) => {
267696
+ const raw = [...this.agent.context.history];
267697
+ const history = personalMemoryRecall === void 0 ? raw : [...raw, personalMemoryRecall];
266310
267698
  const selected = taskToolsNeeded ? history : greetingHistory(history, greetingAnchor);
266311
- partialContext = selected.length < history.length;
266312
- return selected;
267699
+ const projected = await this.historicalToolProjection.project(selected, turnAnchor, tools, {
267700
+ turnId: String(turnId),
267701
+ stepNumber: this.currentStep,
267702
+ signal,
267703
+ llm: turnLLM
267704
+ });
267705
+ const current = this.agent.context.history;
267706
+ if (current.length !== raw.length || current.some((message, index) => message !== raw[index])) {
267707
+ const latest = personalMemoryRecall === void 0 ? current : [...current, personalMemoryRecall];
267708
+ const retained = taskToolsNeeded ? latest : greetingHistory(latest, greetingAnchor);
267709
+ partialContext = retained.length < latest.length;
267710
+ return retained;
267711
+ }
267712
+ partialContext = selected.length < history.length || projected.changed;
267713
+ return projected.history;
266313
267714
  };
266314
267715
  const turnLLM = this.agent.llmForTurn(turnNeedsThinking ? void 0 : "off");
266315
267716
  const turnTools = this.agent.tools.captureTurnTools();
@@ -266345,8 +267746,8 @@ var init_turn = __esmMin((() => {
266345
267746
  }, completion);
266346
267747
  },
266347
267748
  llm: turnLLM,
266348
- buildMessages: () => this.agent.context.project(turnHistory(), { dropOrphanResults: true }),
266349
- buildMessagesStrict: () => this.agent.context.project(turnHistory(), {
267749
+ buildMessages: async () => this.agent.context.project(await turnHistory(selectedTools), { dropOrphanResults: true }),
267750
+ buildMessagesStrict: async () => this.agent.context.project(await turnHistory(selectedTools), {
266350
267751
  synthesizeMissing: true,
266351
267752
  dropOrphanResults: true,
266352
267753
  dedupeDuplicateToolCalls: true,
@@ -269581,6 +270982,7 @@ async function reviewNativeResponse(agent, language, input) {
269581
270982
  });
269582
270983
  };
269583
270984
  const valueType = (value) => value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
270985
+ let formatRetry = false;
269584
270986
  for (let attempt = 1; attempt <= 2; attempt += 1) {
269585
270987
  let receivedBytes = 0;
269586
270988
  let outputLimitExceeded = false;
@@ -269597,7 +270999,7 @@ async function reviewNativeResponse(agent, language, input) {
269597
270999
  attempt: `${attempt}/2`
269598
271000
  }
269599
271001
  };
269600
- const prompt = attempt === 1 ? NATIVE_REVIEW_PROMPT : `${NATIVE_REVIEW_PROMPT}\nThe previous review did not satisfy the required JSON format. Return exactly one object with the two required boolean keys nativeness and orthography, no wrapper or additional keys. Use true or false, not strings or null. Set a check to false if it fails or you are uncertain.`;
271002
+ const prompt = !formatRetry ? NATIVE_REVIEW_PROMPT : `${NATIVE_REVIEW_PROMPT}\nThe previous review did not satisfy the required JSON format. Return exactly one object with the two required boolean keys nativeness and orthography, no wrapper or additional keys. Use true or false, not strings or null. Set a check to false if it fails or you are uncertain.`;
269601
271003
  try {
269602
271004
  signal.throwIfAborted();
269603
271005
  const requested = agent.generate(provider, prompt, [], [createUserMessage(candidateMessage)], { onMessagePart: (part) => {
@@ -269622,7 +271024,10 @@ async function reviewNativeResponse(agent, language, input) {
269622
271024
  parsed = JSON.parse(text);
269623
271025
  } catch {
269624
271026
  record("invalid_json", attempt);
269625
- if (attempt === 1) continue;
271027
+ if (attempt === 1) {
271028
+ formatRetry = true;
271029
+ continue;
271030
+ }
269626
271031
  return;
269627
271032
  }
269628
271033
  if (!isPlainRecord(parsed) || Object.keys(parsed).length !== 2 || typeof parsed["nativeness"] !== "boolean" || typeof parsed["orthography"] !== "boolean") {
@@ -269633,7 +271038,10 @@ async function reviewNativeResponse(agent, language, input) {
269633
271038
  nativenessType: fields ? valueType(fields["nativeness"]) : "missing",
269634
271039
  orthographyType: fields ? valueType(fields["orthography"]) : "missing"
269635
271040
  });
269636
- if (attempt === 1 && !(fields && (fields["nativeness"] === false || fields["orthography"] === false))) continue;
271041
+ if (attempt === 1 && !(fields && (fields["nativeness"] === false || fields["orthography"] === false))) {
271042
+ formatRetry = true;
271043
+ continue;
271044
+ }
269637
271045
  return;
269638
271046
  }
269639
271047
  record(parsed["nativeness"] && parsed["orthography"] ? "approved" : "native_rejected", attempt, {
@@ -269644,7 +271052,12 @@ async function reviewNativeResponse(agent, language, input) {
269644
271052
  nativeness: parsed["nativeness"],
269645
271053
  orthography: parsed["orthography"]
269646
271054
  };
269647
- } catch {
271055
+ } catch (error) {
271056
+ if (!signal.aborted && !outputLimitExceeded && attempt === 1 && error instanceof APIProviderRateLimitError) {
271057
+ record("rate_limited", attempt);
271058
+ if (error.retryAfterMs === null) await agent.providerRetryWait.defer(providerRetryScope(modelAlias, provider), 1e3);
271059
+ continue;
271060
+ }
269648
271061
  return invalid(signal.aborted ? "cancelled" : outputLimitExceeded ? "output_limit" : "generation_failed");
269649
271062
  }
269650
271063
  }
@@ -269699,6 +271112,7 @@ var init_response_review_host = __esmMin((() => {
269699
271112
  init_src$4();
269700
271113
  init_abort();
269701
271114
  init_response_review_mcp();
271115
+ init_provider_retry_wait();
269702
271116
  init_canonical_args();
269703
271117
  NATIVE_REVIEW_PROMPT = [
269704
271118
  "You are an independent language reviewer, not the author of the candidate.",
@@ -334226,1138 +335640,6 @@ var init_blun_media = __esmMin((() => {
334226
335640
  };
334227
335641
  }));
334228
335642
  //#endregion
334229
- //#region ../../packages/kaos/src/errors.ts
334230
- var KaosError, KaosFileExistsError, KaosShellNotFoundError;
334231
- var init_errors$1 = __esmMin((() => {
334232
- KaosError = class extends Error {
334233
- constructor(message) {
334234
- super(message);
334235
- this.name = "KaosError";
334236
- }
334237
- };
334238
- KaosFileExistsError = class extends KaosError {
334239
- constructor(message) {
334240
- super(message);
334241
- this.name = "KaosFileExistsError";
334242
- }
334243
- };
334244
- KaosShellNotFoundError = class extends KaosError {
334245
- constructor(message) {
334246
- super(message);
334247
- this.name = "KaosShellNotFoundError";
334248
- }
334249
- };
334250
- }));
334251
- //#endregion
334252
- //#region ../../packages/kaos/src/environment.ts
334253
- /**
334254
- * Environment — cross-platform probe of OS / shell.
334255
- *
334256
- * Detection is a pure function of injected probes (`platform` / `arch` /
334257
- * `release` / `env` / `isFile` / `execFileText`) so the same suite runs
334258
- * identically on any host OS. `detectEnvironmentFromNode()` bundles the Node
334259
- * defaults for production callers.
334260
- *
334261
- * On Windows the probe expects Git Bash (the canonical POSIX shell that
334262
- * ships with Git for Windows). If it cannot be located the function
334263
- * throws `KaosShellNotFoundError`; the SDK layer can wrap that into a
334264
- * user-facing install hint. Set `BLUN_SHELL_PATH` to override.
334265
- */
334266
- function resolveOsKind(platform) {
334267
- switch (platform) {
334268
- case "darwin": return "macOS";
334269
- case "linux": return "Linux";
334270
- case "win32": return "Windows";
334271
- default: return platform;
334272
- }
334273
- }
334274
- async function detectEnvironment(deps) {
334275
- const osKind = resolveOsKind(deps.platform);
334276
- const osArch = deps.arch;
334277
- const osVersion = deps.release;
334278
- if (deps.platform === "win32") return {
334279
- osKind,
334280
- osArch,
334281
- osVersion,
334282
- shellName: "bash",
334283
- shellPath: await locateWindowsGitBash(deps)
334284
- };
334285
- const candidates = [
334286
- "/bin/bash",
334287
- "/usr/bin/bash",
334288
- "/usr/local/bin/bash"
334289
- ];
334290
- let found;
334291
- for (const p of candidates) if (await deps.isFile(p)) {
334292
- found = p;
334293
- break;
334294
- }
334295
- if (found !== void 0) return {
334296
- osKind,
334297
- osArch,
334298
- osVersion,
334299
- shellName: "bash",
334300
- shellPath: found
334301
- };
334302
- return {
334303
- osKind,
334304
- osArch,
334305
- osVersion,
334306
- shellName: "sh",
334307
- shellPath: "/bin/sh"
334308
- };
334309
- }
334310
- async function locateWindowsGitBash(deps) {
334311
- const checked = [];
334312
- const override = deps.env["BLUN_SHELL_PATH"]?.trim();
334313
- if (override !== void 0 && override.length > 0) {
334314
- checked.push(override);
334315
- if (await deps.isFile(override)) return override;
334316
- }
334317
- const gitExecutables = await findExecutablesOnPath("git.exe", deps.env["PATH"], deps.platform, deps.isFile);
334318
- for (const gitExe of gitExecutables) {
334319
- const inferred = gitBashCandidatesFromGitExe(gitExe);
334320
- if (inferred !== void 0) for (const candidate of inferred) {
334321
- checked.push(candidate);
334322
- if (await deps.isFile(candidate)) return candidate;
334323
- }
334324
- const gitExecPath = await readGitExecPath(deps, gitExe);
334325
- if (gitExecPath === void 0) continue;
334326
- for (const candidate of gitBashCandidatesFromGitExecPath(gitExecPath)) {
334327
- checked.push(candidate);
334328
- if (await deps.isFile(candidate)) return candidate;
334329
- }
334330
- }
334331
- const candidates = [
334332
- "C:\\Program Files\\Git\\bin\\bash.exe",
334333
- "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
334334
- "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
334335
- "C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
334336
- ];
334337
- const localAppData = deps.env["LOCALAPPDATA"]?.trim();
334338
- if (localAppData !== void 0 && localAppData.length > 0) {
334339
- candidates.push(`${localAppData}\\Programs\\Git\\bin\\bash.exe`);
334340
- candidates.push(`${localAppData}\\Programs\\Git\\usr\\bin\\bash.exe`);
334341
- }
334342
- for (const candidate of candidates) {
334343
- checked.push(candidate);
334344
- if (await deps.isFile(candidate)) return candidate;
334345
- }
334346
- throw new KaosShellNotFoundError(`Git Bash was not found on this Windows host. Install Git for Windows from https://gitforwindows.org/ or set BLUN_SHELL_PATH to a bash.exe. Checked: ${checked.join(", ")}.`);
334347
- }
334348
- async function readGitExecPath(deps, gitExe) {
334349
- if (deps.platform === "win32" && !isAbsoluteWindowsPath(gitExe)) return void 0;
334350
- const stdout = await deps.execFileText(gitExe, ["--exec-path"], GIT_EXEC_PATH_TIMEOUT_MS);
334351
- if (stdout === void 0) return void 0;
334352
- for (const line of stdout.split(/\r?\n/)) {
334353
- const execPath = line.trim();
334354
- if (execPath.length > 0) return execPath;
334355
- }
334356
- }
334357
- function gitBashCandidatesFromGitExe(gitExe) {
334358
- const normalizedGitExe = path$18.win32.normalize(normalizeWindowsPath(gitExe));
334359
- const gitDir = path$18.win32.dirname(normalizedGitExe);
334360
- const gitDirName = path$18.win32.basename(gitDir).toLowerCase();
334361
- if (gitDirName !== "cmd" && gitDirName !== "bin") return;
334362
- return gitBashCandidatesFromGitRoot(path$18.win32.dirname(gitDir));
334363
- }
334364
- function gitBashCandidatesFromGitExecPath(execPath) {
334365
- const normalized = path$18.win32.normalize(normalizeWindowsPath(execPath));
334366
- const parts = normalized.split("\\");
334367
- for (let i = parts.length - 1; i >= 0; i -= 1) {
334368
- const segment = parts[i]?.toLowerCase();
334369
- if (segment === "mingw32" || segment === "mingw64") {
334370
- const root = parts.slice(0, i).join("\\");
334371
- if (root.length > 0) return gitBashCandidatesFromGitRoot(root);
334372
- }
334373
- }
334374
- return gitBashCandidatesFromGitRoot(path$18.win32.join(normalized, "..", ".."));
334375
- }
334376
- function gitBashCandidatesFromGitRoot(root) {
334377
- return [path$18.win32.normalize(path$18.win32.join(root, "bin", "bash.exe")), path$18.win32.normalize(path$18.win32.join(root, "usr", "bin", "bash.exe"))];
334378
- }
334379
- function normalizeWindowsPath(path) {
334380
- return path.replaceAll("/", "\\");
334381
- }
334382
- function isAbsoluteWindowsPath(path) {
334383
- return path$18.win32.isAbsolute(normalizeWindowsPath(path));
334384
- }
334385
- function dedupeWindowsPaths(paths) {
334386
- const deduped = [];
334387
- const seen = /* @__PURE__ */ new Set();
334388
- for (const path of paths) {
334389
- const key = normalizeWindowsPath(path).toLowerCase();
334390
- if (seen.has(key)) continue;
334391
- seen.add(key);
334392
- deduped.push(path);
334393
- }
334394
- return deduped;
334395
- }
334396
- function detectEnvironmentFromNode() {
334397
- if (detectedEnvironment !== void 0) return detectedEnvironment;
334398
- const platform = process.platform;
334399
- const env = process.env;
334400
- const isFile = async (path) => {
334401
- try {
334402
- await access(path, constants.F_OK);
334403
- return true;
334404
- } catch {
334405
- return false;
334406
- }
334407
- };
334408
- detectedEnvironment = detectEnvironment({
334409
- platform,
334410
- arch: process.arch,
334411
- release: nodeOs.release(),
334412
- env,
334413
- isFile,
334414
- execFileText: execFileText$1
334415
- });
334416
- return detectedEnvironment;
334417
- }
334418
- async function findExecutablesOnPath(name, pathEnv, platform, isFile) {
334419
- if (pathEnv === void 0 || pathEnv.length === 0) return [];
334420
- const listSep = platform === "win32" ? ";" : ":";
334421
- const dirSep = platform === "win32" ? "\\" : "/";
334422
- const paths = [];
334423
- for (const rawDir of pathEnv.split(listSep)) {
334424
- const dir = rawDir.trim();
334425
- if (dir.length === 0) continue;
334426
- if (platform === "win32" && !isAbsoluteWindowsPath(dir)) continue;
334427
- const candidate = dir.endsWith(dirSep) ? `${dir}${name}` : `${dir}${dirSep}${name}`;
334428
- if (await isFile(candidate)) paths.push(candidate);
334429
- }
334430
- return platform === "win32" ? dedupeWindowsPaths(paths) : paths;
334431
- }
334432
- async function execFileText$1(file, args, timeoutMs) {
334433
- return new Promise((resolve) => {
334434
- execFile(file, [...args], {
334435
- encoding: "utf8",
334436
- timeout: timeoutMs,
334437
- windowsHide: true
334438
- }, (error, stdout) => {
334439
- if (error !== null) {
334440
- resolve(void 0);
334441
- return;
334442
- }
334443
- resolve(stdout);
334444
- });
334445
- });
334446
- }
334447
- var GIT_EXEC_PATH_TIMEOUT_MS, detectedEnvironment;
334448
- var init_environment$1 = __esmMin((() => {
334449
- init_errors$1();
334450
- GIT_EXEC_PATH_TIMEOUT_MS = 5e3;
334451
- }));
334452
- //#endregion
334453
- //#region ../../packages/kaos/src/internal.ts
334454
- function isUtf8Continuation(byte) {
334455
- return byte >= 128 && byte <= 191;
334456
- }
334457
- function decodeUtf8Ignore(data) {
334458
- let output = "";
334459
- let i = 0;
334460
- while (i < data.length) {
334461
- const b0 = data[i];
334462
- if (b0 === void 0) break;
334463
- if (b0 <= 127) {
334464
- output += String.fromCodePoint(b0);
334465
- i += 1;
334466
- continue;
334467
- }
334468
- if (b0 >= 194 && b0 <= 223) {
334469
- const b1 = data[i + 1];
334470
- if (b1 !== void 0 && isUtf8Continuation(b1)) {
334471
- output += String.fromCodePoint((b0 & 31) << 6 | b1 & 63);
334472
- i += 2;
334473
- continue;
334474
- }
334475
- i += 1;
334476
- continue;
334477
- }
334478
- if (b0 >= 224 && b0 <= 239) {
334479
- const b1 = data[i + 1];
334480
- const b2 = data[i + 2];
334481
- 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)) {
334482
- output += String.fromCodePoint((b0 & 15) << 12 | (b1 & 63) << 6 | b2 & 63);
334483
- i += 3;
334484
- continue;
334485
- }
334486
- i += 1;
334487
- continue;
334488
- }
334489
- if (b0 >= 240 && b0 <= 244) {
334490
- const b1 = data[i + 1];
334491
- const b2 = data[i + 2];
334492
- const b3 = data[i + 3];
334493
- 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)) {
334494
- output += String.fromCodePoint((b0 & 7) << 18 | (b1 & 63) << 12 | (b2 & 63) << 6 | b3 & 63);
334495
- i += 4;
334496
- continue;
334497
- }
334498
- i += 1;
334499
- continue;
334500
- }
334501
- i += 1;
334502
- }
334503
- return output;
334504
- }
334505
- function decodeUtf16LeIgnore(data) {
334506
- let output = "";
334507
- let i = 0;
334508
- while (i + 1 < data.length) {
334509
- const first = data[i];
334510
- const second = data[i + 1];
334511
- if (first === void 0 || second === void 0) break;
334512
- const codeUnit = first | second << 8;
334513
- if (codeUnit >= 55296 && codeUnit <= 56319) {
334514
- const lowFirst = data[i + 2];
334515
- const lowSecond = data[i + 3];
334516
- if (lowFirst !== void 0 && lowSecond !== void 0) {
334517
- const low = lowFirst | lowSecond << 8;
334518
- if (low >= 56320 && low <= 57343) {
334519
- const codePoint = 65536 + (codeUnit - 55296 << 10) + (low - 56320);
334520
- output += String.fromCodePoint(codePoint);
334521
- i += 4;
334522
- continue;
334523
- }
334524
- }
334525
- i += 2;
334526
- continue;
334527
- }
334528
- if (codeUnit >= 56320 && codeUnit <= 57343) {
334529
- i += 2;
334530
- continue;
334531
- }
334532
- output += String.fromCodePoint(codeUnit);
334533
- i += 2;
334534
- }
334535
- return output;
334536
- }
334537
- /**
334538
- * Decode a Buffer into a string with Python-compatible `errors` handling.
334539
- *
334540
- * - `'strict'` (default): throw on invalid sequences (via TextDecoder `fatal: true`)
334541
- * - `'replace'`: substitute each invalid sequence with U+FFFD (TextDecoder default)
334542
- * - `'ignore'`: drop invalid input sequences while preserving valid U+FFFD characters
334543
- *
334544
- * Falls back to `Buffer.toString(encoding)` for encodings TextDecoder does not
334545
- * support (e.g. `hex`, `base64`, `binary`, `latin1`) — those are lossless
334546
- * byte-to-character mappings so `errors` has no effect.
334547
- * @internal
334548
- */
334549
- function decodeTextWithErrors(data, encoding, errors = "strict", ignoreBOM = false) {
334550
- let webLabel;
334551
- switch (encoding) {
334552
- case "utf-8":
334553
- case "utf8":
334554
- webLabel = "utf-8";
334555
- break;
334556
- case "utf16le":
334557
- case "ucs2":
334558
- case "ucs-2":
334559
- webLabel = "utf-16le";
334560
- break;
334561
- default: webLabel = void 0;
334562
- }
334563
- if (webLabel === void 0) return data.toString(encoding);
334564
- if (errors === "strict") return new TextDecoder(webLabel, {
334565
- fatal: true,
334566
- ignoreBOM
334567
- }).decode(data);
334568
- if (errors === "ignore") return webLabel === "utf-8" ? decodeUtf8Ignore(data) : decodeUtf16LeIgnore(data);
334569
- return new TextDecoder(webLabel, {
334570
- fatal: false,
334571
- ignoreBOM
334572
- }).decode(data);
334573
- }
334574
- /**
334575
- * Convert a glob pattern segment (e.g. "*.txt", "file?.log") into a RegExp.
334576
- * Mirrors Python pathlib behavior: includes dotfiles, case-sensitive by default.
334577
- * @internal
334578
- */
334579
- function globPatternToRegex(pattern, caseSensitive) {
334580
- let regex = "^";
334581
- for (let i = 0; i < pattern.length; i++) {
334582
- const ch = pattern[i];
334583
- if (ch === void 0) break;
334584
- switch (ch) {
334585
- case "*":
334586
- regex += "[^/]*";
334587
- break;
334588
- case "?":
334589
- regex += "[^/]";
334590
- break;
334591
- case "[": {
334592
- const end = pattern.indexOf("]", i + 1);
334593
- if (end === -1) regex += "\\[";
334594
- else {
334595
- let charClass = pattern.slice(i + 1, end);
334596
- charClass = charClass.replace(/\\/g, "\\\\");
334597
- if (charClass.startsWith("!")) charClass = "^" + charClass.slice(1);
334598
- else if (charClass.startsWith("^")) charClass = "\\" + charClass;
334599
- regex += "[" + charClass + "]";
334600
- i = end;
334601
- }
334602
- break;
334603
- }
334604
- case "\\":
334605
- if (i + 1 < pattern.length) {
334606
- const next = pattern.charAt(i + 1);
334607
- regex += next.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
334608
- i++;
334609
- } else regex += "\\\\";
334610
- break;
334611
- default: regex += ch.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
334612
- }
334613
- }
334614
- regex += "$";
334615
- return new RegExp(regex, caseSensitive ? "" : "i");
334616
- }
334617
- var BufferedReadable;
334618
- var init_internal = __esmMin((() => {
334619
- BufferedReadable = class extends Readable$1 {
334620
- _source;
334621
- _ended = false;
334622
- constructor(source) {
334623
- super({ highWaterMark: 128 * 1024 });
334624
- this._source = source;
334625
- this._source.on("data", this._onData);
334626
- this._source.on("end", this._onEnd);
334627
- this._source.on("close", this._onClose);
334628
- this._source.on("error", this._onError);
334629
- }
334630
- _read() {
334631
- if (!this._ended && !this.destroyed) this._source.resume();
334632
- }
334633
- _destroy(error, callback) {
334634
- this._source.off("data", this._onData);
334635
- this._source.off("end", this._onEnd);
334636
- this._source.off("close", this._onClose);
334637
- this._source.off("error", this._onError);
334638
- this._source.destroy();
334639
- callback(error);
334640
- }
334641
- _onData = (chunk) => {
334642
- if (!this.push(chunk)) this._source.pause();
334643
- };
334644
- _onEnd = () => {
334645
- this._ended = true;
334646
- this.push(null);
334647
- };
334648
- _onClose = () => {
334649
- if (!this._ended) {
334650
- this._ended = true;
334651
- this.push(null);
334652
- }
334653
- };
334654
- _onError = (error) => {
334655
- this.destroy(error);
334656
- };
334657
- };
334658
- }));
334659
- //#endregion
334660
- //#region ../../packages/kaos/src/login-shell-path.ts
334661
- /**
334662
- * Login-shell PATH probe — enrich `process.env.PATH` with entries from the
334663
- * user's login shell.
334664
- *
334665
- * When blun is launched from a context that skipped the user's shell
334666
- * profile (GUI launchers, non-login parent shells), `process.env.PATH`
334667
- * misses entries like `/opt/homebrew/bin`, so commands spawned by the Bash
334668
- * tool can't find tools the user has in their interactive shell (e.g.
334669
- * `gh`). We run the user's login shell once (`$SHELL -l -c /usr/bin/env`),
334670
- * extract its PATH, and append the entries the current PATH lacks. Existing
334671
- * entries keep their order and priority; failures (no resolvable shell,
334672
- * hung or broken profile) silently leave PATH untouched.
334673
- *
334674
- * launchd/daemon launches can leave `$SHELL` unset or blank (see
334675
- * `defaultShell()` in agent-core's terminalService for the same case), so
334676
- * the probe falls back to the OS account's login shell from the user
334677
- * database before giving up.
334678
- *
334679
- * Like `detectEnvironment`, the probe is a pure function of injected deps
334680
- * so the suite runs identically on any host. Windows is skipped: the
334681
- * problem is specific to POSIX login-shell profiles.
334682
- */
334683
- /**
334684
- * Run the user's login shell and return its PATH, or `undefined` when the
334685
- * probe does not apply (Windows, no resolvable shell) or fails (spawn
334686
- * error, timeout, no PATH in the output).
334687
- */
334688
- async function probeLoginShellPath(deps) {
334689
- if (deps.platform === "win32") return void 0;
334690
- const envShell = deps.env["SHELL"]?.trim();
334691
- const shell = envShell === void 0 || envShell.length === 0 ? deps.userShell() : envShell;
334692
- if (shell === void 0 || shell.length === 0) return void 0;
334693
- const stdout = await deps.execFileText(shell, [
334694
- "-l",
334695
- "-c",
334696
- "/usr/bin/env"
334697
- ], LOGIN_SHELL_ENV_TIMEOUT_MS);
334698
- if (stdout === void 0) return void 0;
334699
- let path;
334700
- for (const line of stdout.split("\n")) if (line.startsWith("PATH=")) path = line.slice(5).trim();
334701
- if (path === void 0 || path.length === 0) return void 0;
334702
- return path;
334703
- }
334704
- /**
334705
- * Union of the current PATH and the login-shell PATH: the current PATH
334706
- * string is kept verbatim — including empty components, which POSIX
334707
- * command lookup treats as the current directory — and login-shell
334708
- * entries the current PATH lacks are appended in their own order. When
334709
- * nothing is missing the current string is returned unchanged. Only
334710
- * absolute login-shell entries are imported: empty, `.`, and relative
334711
- * components are all cwd-dependent lookup, and appending one the user
334712
- * did not already have would widen their search path — LocalKaos runs
334713
- * commands from arbitrary workspace directories.
334714
- */
334715
- function mergeLoginShellPath(currentPath, loginShellPath) {
334716
- const current = currentPath ?? "";
334717
- const seen = new Set(current.split(":").filter((entry) => entry.length > 0));
334718
- const additions = [];
334719
- for (const entry of loginShellPath.split(":")) {
334720
- if (!entry.startsWith("/") || seen.has(entry)) continue;
334721
- seen.add(entry);
334722
- additions.push(entry);
334723
- }
334724
- if (additions.length === 0) return current;
334725
- if (currentPath === void 0) return additions.join(":");
334726
- return `${current}:${additions.join(":")}`;
334727
- }
334728
- /** Probe the login shell and merge its PATH into `deps.env['PATH']`. */
334729
- async function applyLoginShellPath(deps) {
334730
- const loginShellPath = await probeLoginShellPath(deps);
334731
- if (loginShellPath === void 0) return;
334732
- const currentPath = deps.env["PATH"];
334733
- const merged = mergeLoginShellPath(currentPath, loginShellPath);
334734
- if (merged === (currentPath ?? "")) return;
334735
- deps.env["PATH"] = merged;
334736
- }
334737
- /**
334738
- * Production convenience — apply the probe to `process.env` once per
334739
- * process. Memoised like `detectEnvironmentFromNode`: the login-shell PATH
334740
- * does not change for the lifetime of the process, and repeated
334741
- * `LocalKaos.create()` calls must not re-spawn the shell.
334742
- */
334743
- /**
334744
- * Login shell from the OS user database (`/etc/passwd` via getpwuid on
334745
- * Linux, Directory Services on macOS). `userInfo()` throws when the uid
334746
- * has no database entry (e.g. containers running an arbitrary uid), and
334747
- * service accounts may carry `/usr/sbin/nologin` — the latter needs no
334748
- * special casing here because probing it simply fails and degrades
334749
- * silently.
334750
- */
334751
- function userShellFromNode() {
334752
- try {
334753
- const shell = userInfo().shell;
334754
- return shell === null || shell.length === 0 ? void 0 : shell;
334755
- } catch {
334756
- return;
334757
- }
334758
- }
334759
- function applyLoginShellPathFromNode() {
334760
- if (appliedLoginShellPath !== void 0) return appliedLoginShellPath;
334761
- appliedLoginShellPath = applyLoginShellPath({
334762
- platform: process.platform,
334763
- env: process.env,
334764
- userShell: userShellFromNode,
334765
- execFileText: execFileText$1
334766
- });
334767
- return appliedLoginShellPath;
334768
- }
334769
- var LOGIN_SHELL_ENV_TIMEOUT_MS, appliedLoginShellPath;
334770
- var init_login_shell_path = __esmMin((() => {
334771
- init_environment$1();
334772
- LOGIN_SHELL_ENV_TIMEOUT_MS = 5e3;
334773
- }));
334774
- //#endregion
334775
- //#region ../../packages/kaos/src/local.ts
334776
- /**
334777
- * Build the `(dev, ino)` cycle-detection key used by `_globWalk`'s
334778
- * visited set. Returns `null` when `ino` is 0, which Node returns on
334779
- * filesystems that don't carry inodes (Windows FAT/exFAT, some SMB/NFS
334780
- * mounts). A null key signals "no reliable identity for this dir" so
334781
- * the caller skips visited tracking for that descent — cycle safety
334782
- * is weakened on those filesystems, but normal walking works instead
334783
- * of every directory colliding on the shared key `"<dev>:0"`.
334784
- */
334785
- function cycleKey(s) {
334786
- if (s.ino === 0) return null;
334787
- return `${String(s.dev)}:${String(s.ino)}`;
334788
- }
334789
- function buildLocalSpawnOptions(_isWindows, cwd, env) {
334790
- return {
334791
- cwd,
334792
- env,
334793
- stdio: [
334794
- "pipe",
334795
- "pipe",
334796
- "pipe"
334797
- ],
334798
- detached: true,
334799
- windowsHide: true
334800
- };
334801
- }
334802
- function isUtf8Encoding(encoding) {
334803
- return encoding === "utf-8" || encoding === "utf8";
334804
- }
334805
- function* splitLinesKeepingTerminator(text) {
334806
- if (text.length === 0) return;
334807
- let start = 0;
334808
- for (let i = 0; i < text.length; i += 1) if (text.codePointAt(i) === 10) {
334809
- yield text.slice(start, i + 1);
334810
- start = i + 1;
334811
- }
334812
- if (start < text.length) yield text.slice(start);
334813
- }
334814
- function updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr) {
334815
- for (let i = 0; i < chunk.length; i += 1) {
334816
- const byte = chunk[i];
334817
- if (byte === void 0) continue;
334818
- if (byte === 13) {
334819
- if (prevWasCr) flags.hasLoneCr = true;
334820
- prevWasCr = true;
334821
- } else if (byte === 10) {
334822
- if (prevWasCr) flags.hasCrLf = true;
334823
- else flags.hasLf = true;
334824
- prevWasCr = false;
334825
- } else {
334826
- if (prevWasCr) flags.hasLoneCr = true;
334827
- prevWasCr = false;
334828
- }
334829
- }
334830
- return prevWasCr;
334831
- }
334832
- function createUtf8Validator() {
334833
- let needed = 0;
334834
- let lower = 128;
334835
- let upper = 191;
334836
- const fail = () => {
334837
- throw new TypeError("Invalid UTF-8 data");
334838
- };
334839
- return {
334840
- write(chunk) {
334841
- for (let i = 0; i < chunk.length; i += 1) {
334842
- const byte = chunk[i];
334843
- if (byte === void 0) continue;
334844
- if (needed === 0) {
334845
- if (byte <= 127) continue;
334846
- if (byte >= 194 && byte <= 223) needed = 1;
334847
- else if (byte === 224) {
334848
- needed = 2;
334849
- lower = 160;
334850
- } else if (byte >= 225 && byte <= 236) needed = 2;
334851
- else if (byte === 237) {
334852
- needed = 2;
334853
- upper = 159;
334854
- } else if (byte >= 238 && byte <= 239) needed = 2;
334855
- else if (byte === 240) {
334856
- needed = 3;
334857
- lower = 144;
334858
- } else if (byte >= 241 && byte <= 243) needed = 3;
334859
- else if (byte === 244) {
334860
- needed = 3;
334861
- upper = 143;
334862
- } else fail();
334863
- } else {
334864
- if (byte < lower || byte > upper) fail();
334865
- lower = 128;
334866
- upper = 191;
334867
- needed -= 1;
334868
- }
334869
- }
334870
- },
334871
- end() {
334872
- if (needed !== 0) fail();
334873
- }
334874
- };
334875
- }
334876
- async function readRange(fh, start, length) {
334877
- const data = Buffer.alloc(length);
334878
- let offset = 0;
334879
- while (offset < length) {
334880
- const { bytesRead } = await fh.read(data, offset, length - offset, start + offset);
334881
- if (bytesRead === 0) break;
334882
- offset += bytesRead;
334883
- }
334884
- return offset === length ? data : data.subarray(0, offset);
334885
- }
334886
- function waitForSpawn(child) {
334887
- return new Promise((resolve, reject) => {
334888
- const onSpawn = () => {
334889
- child.off("error", onError);
334890
- resolve();
334891
- };
334892
- const onError = (err) => {
334893
- child.off("spawn", onSpawn);
334894
- reject(err);
334895
- };
334896
- child.once("spawn", onSpawn);
334897
- child.once("error", onError);
334898
- });
334899
- }
334900
- var isWindows$1, READ_CHUNK_SIZE, LocalProcess, LocalKaos;
334901
- var init_local = __esmMin((() => {
334902
- init_dist$6();
334903
- init_environment$1();
334904
- init_errors$1();
334905
- init_internal();
334906
- init_login_shell_path();
334907
- isWindows$1 = process.platform === "win32";
334908
- READ_CHUNK_SIZE = 64 * 1024;
334909
- LocalProcess = class {
334910
- stdin;
334911
- stdout;
334912
- stderr;
334913
- pid;
334914
- _child;
334915
- _exitCode = null;
334916
- _exitPromise;
334917
- _disposed = false;
334918
- constructor(child) {
334919
- if (child.stdin === null || child.stdout === null || child.stderr === null) throw new Error("Process must be created with stdin/stdout/stderr pipes.");
334920
- this._child = child;
334921
- this.stdin = child.stdin;
334922
- this.stdout = new BufferedReadable(child.stdout);
334923
- this.stderr = new BufferedReadable(child.stderr);
334924
- this.pid = child.pid ?? -1;
334925
- this._exitPromise = new Promise((resolve, reject) => {
334926
- child.on("exit", (code) => {
334927
- this._exitCode = code ?? -1;
334928
- resolve(this._exitCode);
334929
- });
334930
- child.on("error", (error) => {
334931
- reject(error);
334932
- });
334933
- });
334934
- }
334935
- get exitCode() {
334936
- return this._exitCode;
334937
- }
334938
- async wait() {
334939
- return this._exitPromise;
334940
- }
334941
- kill(signal) {
334942
- if (this.pid <= 0) return Promise.resolve();
334943
- if (isWindows$1) {
334944
- const taskkillArgs = [
334945
- "/T",
334946
- "/F",
334947
- "/PID",
334948
- String(this.pid)
334949
- ];
334950
- return new Promise((resolve) => {
334951
- const killer = spawn("taskkill", taskkillArgs, {
334952
- stdio: "ignore",
334953
- windowsHide: true
334954
- });
334955
- const done = () => {
334956
- resolve();
334957
- };
334958
- killer.once("error", done);
334959
- killer.once("close", done);
334960
- });
334961
- }
334962
- try {
334963
- process.kill(-this.pid, signal ?? "SIGTERM");
334964
- } catch (error) {
334965
- const err = error;
334966
- if (err.code === "ESRCH") return Promise.resolve();
334967
- if (err.code === "EPERM") {
334968
- try {
334969
- this._child.kill(signal ?? "SIGTERM");
334970
- } catch {}
334971
- return Promise.resolve();
334972
- }
334973
- throw error;
334974
- }
334975
- return Promise.resolve();
334976
- }
334977
- dispose() {
334978
- if (this._disposed) return;
334979
- this._disposed = true;
334980
- this.stdin.destroy();
334981
- this.stdout.destroy();
334982
- this.stderr.destroy();
334983
- }
334984
- };
334985
- LocalKaos = class LocalKaos {
334986
- name = "local";
334987
- osEnv;
334988
- _cwd;
334989
- _envLayers;
334990
- constructor(osEnv, cwd, envLayers = []) {
334991
- this._cwd = normalize$4(cwd ?? process.cwd());
334992
- this.osEnv = osEnv;
334993
- this._envLayers = envLayers;
334994
- }
334995
- /**
334996
- * Construct a fresh `LocalKaos` after probing the host environment.
334997
- *
334998
- * Each call returns a new instance with its own `_cwd`; concurrent
334999
- * callers can therefore operate on independent working directories
335000
- * without polluting one another.
335001
- */
335002
- static async create() {
335003
- const [osEnv] = await Promise.all([detectEnvironmentFromNode(), applyLoginShellPathFromNode()]);
335004
- return new LocalKaos(osEnv);
335005
- }
335006
- withCwd(cwd) {
335007
- return new LocalKaos(this.osEnv, cwd, this._envLayers);
335008
- }
335009
- withEnv(env) {
335010
- return new LocalKaos(this.osEnv, this._cwd, [...this._envLayers, env]);
335011
- }
335012
- _resolvePath(path) {
335013
- if (isAbsolute$2(path)) return normalize$4(path);
335014
- return join$4(this._cwd, path);
335015
- }
335016
- pathClass() {
335017
- return isWindows$1 ? "win32" : "posix";
335018
- }
335019
- normpath(path) {
335020
- return normalize$4(path);
335021
- }
335022
- gethome() {
335023
- return normalize$4(homedir());
335024
- }
335025
- getcwd() {
335026
- return this._cwd;
335027
- }
335028
- /**
335029
- * Change the working directory of this LocalKaos instance.
335030
- *
335031
- * Unlike Python's `os.chdir`, this is instance-scoped and never touches
335032
- * `process.cwd()`. Child processes spawned via {@link exec} inherit this
335033
- * instance's `_cwd`; concurrent LocalKaos instances each carry their own
335034
- * independent cwd. If you need Python-compatible process-global cwd,
335035
- * call `process.chdir(x)` directly.
335036
- */
335037
- async chdir(path) {
335038
- const resolved = this._resolvePath(path);
335039
- if (!(await stat(resolved)).isDirectory()) throw new Error(`Not a directory: ${resolved}`);
335040
- this._cwd = resolved;
335041
- }
335042
- async stat(path, options) {
335043
- const resolved = this._resolvePath(path);
335044
- const s = options?.followSymlinks ?? true ? await stat(resolved) : await lstat(resolved);
335045
- return {
335046
- stMode: s.mode,
335047
- stIno: s.ino,
335048
- stDev: s.dev,
335049
- stNlink: s.nlink,
335050
- stUid: s.uid,
335051
- stGid: s.gid,
335052
- stSize: s.size,
335053
- stAtime: s.atimeMs / 1e3,
335054
- stMtime: s.mtimeMs / 1e3,
335055
- stCtime: isWindows$1 ? s.birthtimeMs / 1e3 : s.ctimeMs / 1e3
335056
- };
335057
- }
335058
- async *iterdir(path) {
335059
- const resolved = this._resolvePath(path);
335060
- const entries = await readdir(resolved);
335061
- for (const entry of entries) yield join$4(resolved, entry);
335062
- }
335063
- async *glob(path, pattern, options) {
335064
- const resolved = this._resolvePath(path);
335065
- const caseSensitive = options?.caseSensitive ?? true;
335066
- const patternParts = pattern.split("/");
335067
- const initVisited = /* @__PURE__ */ new Set();
335068
- try {
335069
- const rootKey = cycleKey(await stat(resolved));
335070
- if (rootKey !== null) initVisited.add(rootKey);
335071
- } catch {}
335072
- yield* this._globWalk(resolved, patternParts, caseSensitive, initVisited);
335073
- }
335074
- async *_globWalk(basePath, patternParts, caseSensitive, visited) {
335075
- if (patternParts.length === 0) return;
335076
- const [currentPattern, ...remainingParts] = patternParts;
335077
- if (currentPattern === "**") {
335078
- if (remainingParts.length > 0) yield* this._globWalk(basePath, remainingParts, caseSensitive, visited);
335079
- else yield basePath;
335080
- let entries;
335081
- try {
335082
- entries = await readdir(basePath);
335083
- } catch {
335084
- return;
335085
- }
335086
- for (const entry of entries) {
335087
- const fullPath = join$4(basePath, entry);
335088
- let entryStat;
335089
- try {
335090
- entryStat = await stat(fullPath);
335091
- } catch {
335092
- continue;
335093
- }
335094
- if (entryStat.isDirectory()) {
335095
- const key = cycleKey(entryStat);
335096
- if (key !== null && visited.has(key)) continue;
335097
- yield* this._globWalk(fullPath, patternParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
335098
- } else if (remainingParts.length === 0) yield fullPath;
335099
- }
335100
- } else {
335101
- const regex = globPatternToRegex(currentPattern ?? "", caseSensitive);
335102
- let entries;
335103
- try {
335104
- entries = await readdir(basePath);
335105
- } catch {
335106
- return;
335107
- }
335108
- for (const entry of entries) {
335109
- if (!regex.test(entry)) continue;
335110
- const fullPath = join$4(basePath, entry);
335111
- if (remainingParts.length === 0) yield fullPath;
335112
- else {
335113
- let entryStat;
335114
- try {
335115
- entryStat = await stat(fullPath);
335116
- } catch {
335117
- continue;
335118
- }
335119
- if (entryStat.isDirectory()) {
335120
- const key = cycleKey(entryStat);
335121
- if (key !== null && visited.has(key)) continue;
335122
- yield* this._globWalk(fullPath, remainingParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
335123
- }
335124
- }
335125
- }
335126
- }
335127
- }
335128
- async readBytes(path, n) {
335129
- const resolved = this._resolvePath(path);
335130
- if (n === void 0) return Buffer.from(await readFile(resolved));
335131
- const fh = await open(resolved, "r");
335132
- try {
335133
- const buf = Buffer.alloc(n);
335134
- const { bytesRead } = await fh.read(buf, 0, n, 0);
335135
- return buf.subarray(0, bytesRead);
335136
- } finally {
335137
- await fh.close();
335138
- }
335139
- }
335140
- async readText(path, options) {
335141
- const resolved = this._resolvePath(path);
335142
- const encoding = options?.encoding ?? "utf-8";
335143
- const errors = options?.errors ?? "strict";
335144
- return decodeTextWithErrors(await readFile(resolved), encoding, errors);
335145
- }
335146
- async *readLines(path, options) {
335147
- const resolved = this._resolvePath(path);
335148
- const encoding = options?.encoding ?? "utf-8";
335149
- const errors = options?.errors ?? "strict";
335150
- if (!isUtf8Encoding(encoding)) {
335151
- yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readFile(resolved), encoding, errors));
335152
- return;
335153
- }
335154
- yield* this._readUtf8Lines(resolved, errors);
335155
- }
335156
- async scanTextFile(path) {
335157
- const fh = await open(this._resolvePath(path), "r");
335158
- try {
335159
- const buf = Buffer.alloc(READ_CHUNK_SIZE);
335160
- const flags = {
335161
- hasCrLf: false,
335162
- hasLf: false,
335163
- hasLoneCr: false
335164
- };
335165
- const validator = createUtf8Validator();
335166
- let totalLines = 0;
335167
- let totalBytes = 0;
335168
- let endsWithNewline = false;
335169
- let hasNul = false;
335170
- let prevWasCr = false;
335171
- while (true) {
335172
- const { bytesRead } = await fh.read(buf, 0, buf.length, null);
335173
- if (bytesRead === 0) break;
335174
- const chunk = buf.subarray(0, bytesRead);
335175
- validator.write(chunk);
335176
- for (let i = 0; i < chunk.length; i += 1) {
335177
- const byte = chunk[i];
335178
- if (byte === void 0) continue;
335179
- if (byte === 0) hasNul = true;
335180
- if (byte === 10) totalLines += 1;
335181
- }
335182
- prevWasCr = updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr);
335183
- totalBytes += bytesRead;
335184
- endsWithNewline = chunk[bytesRead - 1] === 10;
335185
- }
335186
- if (prevWasCr) flags.hasLoneCr = true;
335187
- validator.end();
335188
- if (totalBytes > 0 && !endsWithNewline) totalLines += 1;
335189
- return {
335190
- totalLines,
335191
- endsWithNewline,
335192
- hasNul,
335193
- lineEndingFlags: flags
335194
- };
335195
- } finally {
335196
- await fh.close();
335197
- }
335198
- }
335199
- async *readLineRange(path, options) {
335200
- const resolved = this._resolvePath(path);
335201
- const errors = options.errors ?? "strict";
335202
- yield* this._readUtf8Lines(resolved, errors, {
335203
- startLine: options.startLine,
335204
- maxLines: options.maxLines
335205
- });
335206
- }
335207
- async *readTailLines(path, options) {
335208
- if (options.tailCount <= 0) return;
335209
- const resolved = this._resolvePath(path);
335210
- const errors = options.errors ?? "strict";
335211
- const fh = await open(resolved, "r");
335212
- try {
335213
- const s = await fh.stat();
335214
- if (s.size === 0) return;
335215
- let pos = s.size;
335216
- let foundLf = 0;
335217
- let startOffset = 0;
335218
- let needLf = options.tailCount;
335219
- let sawTailBlock = false;
335220
- while (pos > 0 && foundLf < needLf) {
335221
- const readSize = Math.min(READ_CHUNK_SIZE, pos);
335222
- pos -= readSize;
335223
- const buf = Buffer.alloc(readSize);
335224
- await fh.read(buf, 0, readSize, pos);
335225
- if (!sawTailBlock) {
335226
- sawTailBlock = true;
335227
- needLf = buf[readSize - 1] === 10 ? options.tailCount + 1 : options.tailCount;
335228
- }
335229
- for (let i = readSize - 1; i >= 0; i -= 1) {
335230
- if (buf[i] !== 10) continue;
335231
- foundLf += 1;
335232
- if (foundLf === needLf) {
335233
- startOffset = pos + i + 1;
335234
- break;
335235
- }
335236
- }
335237
- }
335238
- if (foundLf < needLf) startOffset = 0;
335239
- yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readRange(fh, startOffset, s.size - startOffset), "utf-8", errors, startOffset !== 0));
335240
- } finally {
335241
- await fh.close();
335242
- }
335243
- }
335244
- async *_readUtf8Lines(resolved, errors, range) {
335245
- const startLine = range?.startLine ?? 1;
335246
- const maxLines = range?.maxLines ?? Number.POSITIVE_INFINITY;
335247
- const fh = await open(resolved, "r");
335248
- try {
335249
- const buf = Buffer.alloc(READ_CHUNK_SIZE);
335250
- let pending = [];
335251
- let pendingOffset = 0;
335252
- let fileOffset = 0;
335253
- let lineNo = 1;
335254
- let yielded = 0;
335255
- while (true) {
335256
- const { bytesRead } = await fh.read(buf, 0, buf.length, null);
335257
- if (bytesRead === 0) break;
335258
- const chunk = buf.subarray(0, bytesRead);
335259
- let lineStart = 0;
335260
- for (let i = 0; i < chunk.length; i += 1) {
335261
- if (chunk[i] !== 10) continue;
335262
- const piece = chunk.subarray(lineStart, i + 1);
335263
- const lineOffset = pending.length === 0 ? fileOffset + lineStart : pendingOffset;
335264
- const line = pending.length === 0 ? piece : Buffer.concat([...pending, piece]);
335265
- if (lineNo >= startLine) {
335266
- yield decodeTextWithErrors(line, "utf-8", errors, lineOffset !== 0);
335267
- yielded += 1;
335268
- if (yielded >= maxLines) return;
335269
- }
335270
- pending = [];
335271
- lineStart = i + 1;
335272
- lineNo += 1;
335273
- }
335274
- if (lineStart < chunk.length) {
335275
- const tail = Buffer.from(chunk.subarray(lineStart));
335276
- if (pending.length === 0) pendingOffset = fileOffset + lineStart;
335277
- pending.push(tail);
335278
- }
335279
- fileOffset += bytesRead;
335280
- }
335281
- if (pending.length > 0) {
335282
- const line = Buffer.concat(pending);
335283
- if (lineNo >= startLine) yield decodeTextWithErrors(line, "utf-8", errors, pendingOffset !== 0);
335284
- }
335285
- } finally {
335286
- await fh.close();
335287
- }
335288
- }
335289
- async writeBytes(path, data) {
335290
- await writeFile(this._resolvePath(path), data);
335291
- return data.length;
335292
- }
335293
- async writeText(path, data, options) {
335294
- const resolved = this._resolvePath(path);
335295
- const encoding = options?.encoding ?? "utf-8";
335296
- if ((options?.mode ?? "w") === "a") await appendFile(resolved, data, encoding);
335297
- else await writeFile(resolved, data, encoding);
335298
- return data.length;
335299
- }
335300
- async mkdir(path, options) {
335301
- const resolved = this._resolvePath(path);
335302
- const parents = options?.parents ?? false;
335303
- const existOk = options?.existOk ?? false;
335304
- if (parents) {
335305
- if (!existOk) try {
335306
- if ((await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists`);
335307
- } catch (error) {
335308
- if (error instanceof KaosFileExistsError) throw error;
335309
- if (error.code !== "ENOENT") throw error;
335310
- }
335311
- await mkdir(resolved, { recursive: true });
335312
- return;
335313
- }
335314
- try {
335315
- await mkdir(resolved);
335316
- } catch (error) {
335317
- if (existOk && error instanceof Error && "code" in error && error.code === "EEXIST") {
335318
- if (!(await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists but is not a directory`);
335319
- return;
335320
- }
335321
- throw error;
335322
- }
335323
- }
335324
- async exec(...args) {
335325
- const command = args[0];
335326
- if (command === void 0) throw new Error("LocalKaos.exec(): at least one argument (the command to run) is required.");
335327
- const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv()));
335328
- await waitForSpawn(child);
335329
- return new LocalProcess(child);
335330
- }
335331
- async execWithEnv(args, env) {
335332
- const command = args[0];
335333
- if (command === void 0) throw new Error("LocalKaos.execWithEnv(): at least one argument (the command to run) is required.");
335334
- const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv(env)));
335335
- await waitForSpawn(child);
335336
- return new LocalProcess(child);
335337
- }
335338
- _buildExecEnv(invocationEnv) {
335339
- if (this._envLayers.length === 0) return invocationEnv;
335340
- const merged = {
335341
- ...process.env,
335342
- ...invocationEnv
335343
- };
335344
- for (const layer of this._envLayers) Object.assign(merged, layer);
335345
- return merged;
335346
- }
335347
- };
335348
- }));
335349
- var init_current = __esmMin((() => {
335350
- new AsyncLocalStorage();
335351
- }));
335352
- //#endregion
335353
- //#region ../../packages/kaos/src/index.ts
335354
- var init_src$1 = __esmMin((() => {
335355
- init_environment$1();
335356
- init_errors$1();
335357
- init_local();
335358
- init_current();
335359
- }));
335360
- //#endregion
335361
335643
  //#region ../../packages/agent-core/src/agent/replay/build.ts
335362
335644
  async function buildReplay(persistence, range) {
335363
335645
  const agent = new Agent$1({
@@ -531879,6 +532161,7 @@ var SessionEventHandler = class {
531879
532161
  }
531880
532162
  }
531881
532163
  handleSessionError(event) {
532164
+ if (event.code === "turn.agent_busy" && this.host.isManualPromptAdmissionPending?.(event.sessionId, event.agentId)) return;
531882
532165
  this.host.streamingUI.flushNow();
531883
532166
  this.host.streamingUI.resetToolUi();
531884
532167
  this.host.streamingUI.finalizeLiveTextBuffers("idle");
@@ -539188,17 +539471,22 @@ var BlunTUI = class {
539188
539471
  editorReplacement;
539189
539472
  editorReplacementGeneration = 0;
539190
539473
  preserveQueueAcrossSessionReset = false;
539474
+ manualPromptInFlight;
539475
+ isManualPromptAdmissionPending(sessionId, agentId) {
539476
+ const pending = this.manualPromptInFlight;
539477
+ return pending !== void 0 && this.session === pending.session && pending.session.id === sessionId && pending.agentId === agentId;
539478
+ }
539191
539479
  hasWaitingChannelMessage() {
539192
539480
  return this.state.queuedMessages.some((item) => item.mode === "channel");
539193
539481
  }
539194
539482
  canInterruptForQueuedChannel() {
539195
539483
  const item = this.state.queuedMessages[0];
539196
539484
  if (/^[1-9]\d*$/u.test(this.pendingChannelReplyGuard?.chatId ?? "") && item?.channelChatId?.startsWith("-") === true) return false;
539197
- return item?.mode === "channel" && item.channelContextOnly !== true && this.session !== void 0 && this.queueSteerInFlight === void 0 && !this.deferUserMessages && !this.state.appState.isCompacting && this.state.appState.streamingPhase !== "idle" && this.state.appState.streamingPhase !== "shell" && this.shellOutputStreams.size === 0 && !this.streamingUI.hasActiveToolCalls();
539485
+ return item?.mode === "channel" && item.channelContextOnly !== true && this.session !== void 0 && this.queueSteerInFlight === void 0 && this.manualPromptInFlight === void 0 && !this.deferUserMessages && !this.state.appState.isCompacting && this.state.appState.streamingPhase !== "idle" && this.state.appState.streamingPhase !== "shell" && this.shellOutputStreams.size === 0 && !this.streamingUI.hasActiveToolCalls();
539198
539486
  }
539199
539487
  canDeliverQueuedChannelHead() {
539200
539488
  const item = this.state.queuedMessages[0];
539201
- if (this.isShuttingDown || this.queueCommandRunning || this.queueSteerInFlight !== void 0 || this.editorReplacementActive || this.deferUserMessages || this.session === void 0 || this.state.appState.model.trim().length === 0 || item?.mode !== "channel") return false;
539489
+ if (this.isShuttingDown || this.queueCommandRunning || this.queueSteerInFlight !== void 0 || this.manualPromptInFlight !== void 0 || this.editorReplacementActive || this.deferUserMessages || this.session === void 0 || this.state.appState.model.trim().length === 0 || item?.mode !== "channel") return false;
539202
539490
  const guard = this.pendingChannelReplyGuard;
539203
539491
  return guard === void 0 || item.channelChatId !== void 0 && guard.chatId === item.channelChatId;
539204
539492
  }
@@ -539322,7 +539610,7 @@ var BlunTUI = class {
539322
539610
  canDrainQueue() {
539323
539611
  const head = this.state.queuedMessages[0];
539324
539612
  const remoteControl = head?.mode === "command" && head.channelCommandEnvelope !== void 0 && channelCommandCanInterrupt(head.text);
539325
- return !this.isShuttingDown && !this.queueCommandRunning && this.queueSteerInFlight === void 0 && !this.editorReplacementActive && !this.deferUserMessages && (remoteControl || this.state.appState.streamingPhase === "idle" && !this.state.appState.isCompacting);
539613
+ return !this.isShuttingDown && !this.queueCommandRunning && this.queueSteerInFlight === void 0 && this.manualPromptInFlight === void 0 && !this.editorReplacementActive && !this.deferUserMessages && (remoteControl || this.state.appState.streamingPhase === "idle" && !this.state.appState.isCompacting);
539326
539614
  }
539327
539615
  scheduleQueueDrain() {
539328
539616
  if (this.queueDrainTimer !== void 0 || !this.canDrainQueue()) return;
@@ -539350,11 +539638,12 @@ var BlunTUI = class {
539350
539638
  }
539351
539639
  const inFlightCount = this.queueSteerInFlight?.items.length ?? 0;
539352
539640
  this.queueFlushBatchRemaining = inFlightCount + this.state.queuedMessages.length;
539641
+ if (this.manualPromptInFlight !== void 0) this.manualPromptInFlight.flushRequested = this.queueFlushBatchRemaining > 0;
539353
539642
  this.steerQueueFlushPrefix();
539354
539643
  this.scheduleQueueDrain();
539355
539644
  }
539356
539645
  steerQueueFlushPrefix() {
539357
- if (this.queueSteerInFlight !== void 0 || this.queueFlushBatchRemaining === 0 || this.deferUserMessages || this.state.appState.isCompacting || this.state.appState.streamingPhase === "idle" || this.state.appState.streamingPhase === "shell") return;
539646
+ if (this.queueSteerInFlight !== void 0 || this.manualPromptInFlight !== void 0 || this.queueFlushBatchRemaining === 0 || this.deferUserMessages || this.state.appState.isCompacting || this.state.appState.streamingPhase === "idle" || this.state.appState.streamingPhase === "shell") return;
539358
539647
  const session = this.session;
539359
539648
  if (session === void 0 || this.state.appState.model.trim().length === 0) {
539360
539649
  this.showError(llmNotSetMessage());
@@ -539378,13 +539667,16 @@ var BlunTUI = class {
539378
539667
  turnEnded: false,
539379
539668
  onCommit: () => {
539380
539669
  this.queueFlushBatchRemaining = Math.max(0, this.queueFlushBatchRemaining - items.length);
539381
- for (const item of items) this.appendTranscriptEntry({
539382
- id: nextTranscriptId(),
539383
- kind: "user",
539384
- turnId,
539385
- renderMode: "plain",
539386
- content: item.text.trim()
539387
- });
539670
+ for (const item of items) {
539671
+ if (item.transcriptRendered) continue;
539672
+ this.appendTranscriptEntry({
539673
+ id: nextTranscriptId(),
539674
+ kind: "user",
539675
+ turnId,
539676
+ renderMode: "plain",
539677
+ content: item.text.trim()
539678
+ });
539679
+ }
539388
539680
  }
539389
539681
  };
539390
539682
  this.queueSteerInFlight = inFlight;
@@ -539456,7 +539748,7 @@ var BlunTUI = class {
539456
539748
  channelCommandEnvelope: envelope,
539457
539749
  channelAcknowledge: acknowledge
539458
539750
  };
539459
- const busy = this.state.queuedMessages.length > 0 || this.queueSteerInFlight !== void 0 || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting;
539751
+ const busy = this.state.queuedMessages.length > 0 || this.queueSteerInFlight !== void 0 || this.manualPromptInFlight !== void 0 || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting;
539460
539752
  if (this.session === void 0 || this.deferUserMessages || this.queueCommandRunning || this.editorReplacementActive || busy && !channelCommandCanInterrupt(command)) {
539461
539753
  this.state.queuedMessages.push(item);
539462
539754
  this.scheduleQueueDrain();
@@ -539467,7 +539759,7 @@ var BlunTUI = class {
539467
539759
  }
539468
539760
  injectChannelEnvelope({
539469
539761
  canDeliver: () => this.session !== void 0 && this.state.appState.model.trim().length > 0,
539470
- isBusy: () => this.queueCommandRunning || this.state.queuedMessages.length > 0 || this.queueSteerInFlight !== void 0 || this.deferUserMessages || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting,
539762
+ isBusy: () => this.queueCommandRunning || this.state.queuedMessages.length > 0 || this.queueSteerInFlight !== void 0 || this.manualPromptInFlight !== void 0 || this.deferUserMessages || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting,
539471
539763
  deliverNow: (modelInput, displayText, origin, contextOnly) => {
539472
539764
  this.sendChannelMessageInternal(this.requireSession(), modelInput, displayText, origin, envelope.meta.chat_id, envelope.meta["image_path"], contextOnly, false, acknowledge);
539473
539765
  },
@@ -539771,7 +540063,8 @@ var BlunTUI = class {
539771
540063
  agentId: this.harness.interactiveAgentId,
539772
540064
  parts: options?.parts,
539773
540065
  imageAttachmentIds: options?.imageAttachmentIds !== void 0 && options.imageAttachmentIds.length > 0 ? options.imageAttachmentIds : void 0,
539774
- mode
540066
+ mode,
540067
+ transcriptRendered: options?.transcriptRendered
539775
540068
  });
539776
540069
  this.track("input_queue");
539777
540070
  }
@@ -539830,7 +540123,8 @@ var BlunTUI = class {
539830
540123
  this.harness.withInteractiveAgent(item.agentId ?? "main", () => {
539831
540124
  this.sendMessageInternal(activeSession, item.text, {
539832
540125
  parts: item.parts,
539833
- imageAttachmentIds: item.imageAttachmentIds
540126
+ imageAttachmentIds: item.imageAttachmentIds,
540127
+ transcriptRendered: item.transcriptRendered
539834
540128
  });
539835
540129
  });
539836
540130
  }
@@ -539849,9 +540143,13 @@ var BlunTUI = class {
539849
540143
  this.sessionEventHandler.requestQueuedGoalPromotion();
539850
540144
  }
539851
540145
  sendMessageInternal(session, input, options) {
540146
+ if (this.manualPromptInFlight !== void 0) {
540147
+ this.enqueueMessage(input, options);
540148
+ return;
540149
+ }
539852
540150
  armPersonalMemoryRememberIntent(session.id, input);
539853
540151
  const imageAttachmentIds = options?.imageAttachmentIds !== void 0 && options.imageAttachmentIds.length > 0 ? options.imageAttachmentIds : void 0;
539854
- this.appendTranscriptEntry({
540152
+ if (!options?.transcriptRendered) this.appendTranscriptEntry({
539855
540153
  id: nextTranscriptId(),
539856
540154
  kind: "user",
539857
540155
  turnId: void 0,
@@ -539859,13 +540157,48 @@ var BlunTUI = class {
539859
540157
  content: input,
539860
540158
  imageAttachmentIds
539861
540159
  });
540160
+ const agentId = this.harness.interactiveAgentId;
540161
+ const inFlight = {
540162
+ session,
540163
+ agentId
540164
+ };
540165
+ this.manualPromptInFlight = inFlight;
540166
+ let admissionResolved = false;
539862
540167
  this.beginSessionRequest();
539863
540168
  const sdkInput = options?.parts ?? input;
539864
540169
  this.setAppState({
539865
540170
  model: BLUN_KING_MODEL_ALIAS,
539866
540171
  modelFallbackAllowed: false
539867
540172
  });
539868
- session.promptAccepted(sdkInput).catch((error) => {
540173
+ session.promptAccepted(sdkInput).then((result) => {
540174
+ if (this.session !== session || this.manualPromptInFlight !== inFlight) return;
540175
+ admissionResolved = true;
540176
+ this.manualPromptInFlight = void 0;
540177
+ if (!result.accepted) {
540178
+ finishPersonalMemoryRememberIntentTurn(session.id);
540179
+ if (inFlight.flushRequested && this.queueFlushBatchRemaining > 0) this.queueFlushBatchRemaining += 1;
540180
+ this.state.queuedMessages = [{
540181
+ text: input,
540182
+ agentId,
540183
+ parts: options?.parts,
540184
+ imageAttachmentIds,
540185
+ transcriptRendered: true
540186
+ }, ...this.state.queuedMessages];
540187
+ this.track("input_queue");
540188
+ this.updateQueueDisplay();
540189
+ this.state.ui.requestRender();
540190
+ }
540191
+ this.syncChannelQueueDeadline();
540192
+ if (inFlight.flushRequested) this.steerQueueFlushPrefix();
540193
+ this.scheduleQueueDrain();
540194
+ }).catch((error) => {
540195
+ if (this.session !== session) return;
540196
+ if (admissionResolved) {
540197
+ this.showError(formatErrorMessage$2(error));
540198
+ return;
540199
+ }
540200
+ if (this.manualPromptInFlight !== inFlight) return;
540201
+ this.manualPromptInFlight = void 0;
539869
540202
  finishPersonalMemoryRememberIntentTurn(session.id);
539870
540203
  const message = formatErrorMessage$2(error);
539871
540204
  this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: message }));
@@ -539892,19 +540225,20 @@ var BlunTUI = class {
539892
540225
  });
539893
540226
  }
539894
540227
  sendMessage(session, input, options) {
539895
- if (this.deferUserMessages || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting) {
540228
+ if (this.deferUserMessages || this.manualPromptInFlight !== void 0 || this.state.queuedMessages.length > 0 || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting) {
539896
540229
  this.enqueueMessage(input, options);
540230
+ this.scheduleQueueDrain();
539897
540231
  return;
539898
540232
  }
539899
540233
  this.sendMessageInternal(session, input, options);
539900
540234
  }
539901
540235
  steerMessage(session, input) {
539902
- if (this.deferUserMessages || this.state.appState.isCompacting) {
540236
+ if (this.deferUserMessages || this.state.appState.isCompacting || this.manualPromptInFlight !== void 0) {
539903
540237
  for (const part of input) this.enqueueMessage(part);
539904
540238
  return;
539905
540239
  }
539906
540240
  if (this.state.appState.streamingPhase === "idle") {
539907
- for (const part of input) this.sendMessageInternal(session, part);
540241
+ for (const part of input) this.sendMessage(session, part);
539908
540242
  return;
539909
540243
  }
539910
540244
  for (const part of input) this.appendTranscriptEntry({
@@ -539924,6 +540258,7 @@ var BlunTUI = class {
539924
540258
  this.state.startupState = "ready";
539925
540259
  }
539926
540260
  clearQueuedMessages() {
540261
+ this.manualPromptInFlight = void 0;
539927
540262
  const discarded = [...this.state.queuedMessages, ...this.queueSteerInFlight?.items ?? []];
539928
540263
  this.queueSteerInFlight?.onRestore?.();
539929
540264
  this.state.queuedMessages = [];
@@ -540239,6 +540574,7 @@ var BlunTUI = class {
540239
540574
  this.state.terminal.setTitle(label);
540240
540575
  }
540241
540576
  resetSessionRuntime() {
540577
+ this.manualPromptInFlight = void 0;
540242
540578
  this.streamingUI.setSessionTotalTokens(void 0);
540243
540579
  this.loopCountdown.dispose();
540244
540580
  this.aborted = false;