blun-king-cli 9.1.599 → 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.
Files changed (107) hide show
  1. package/CHANGELOG.md +16 -2
  2. package/LIESMICH.txt +1 -1
  3. package/README.md +13 -3
  4. package/agent-spine-plugin/CHANGELOG.md +1 -1
  5. package/agent-spine-plugin/docs/host-integration.md +1 -15
  6. package/agent-spine-plugin/docs/preflight-recall.md +1 -1
  7. package/agent-spine-plugin/scripts/check-install.js +1 -6
  8. package/agent-spine-plugin/scripts/hermetic-process.js +8 -0
  9. package/agent-spine-plugin/scripts/release-check.js +2 -3
  10. package/agent-spine-plugin/scripts/run-tests-hermetic.js +8 -11
  11. package/agent-spine-plugin/src/hook.js +40 -40
  12. package/agent-spine-plugin/src/lib/briefing.js +6 -3
  13. package/agent-spine-plugin/src/lib/hook-context.js +63 -64
  14. package/agent-spine-plugin/src/lib/hook-output.js +115 -94
  15. package/agent-spine-plugin/src/lib/hook-timeline.js +20 -11
  16. package/agent-spine-plugin/src/lib/host-instruction-budget.js +8 -14
  17. package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +0 -7
  18. package/agent-spine-plugin/src/lib/pre-answer-timeline-recall.js +104 -0
  19. package/agent-spine-plugin/src/lib/session-timeline-codex.js +0 -4
  20. package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +0 -3
  21. package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +0 -4
  22. package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +0 -2
  23. package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +0 -22
  24. package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +0 -4
  25. package/agent-spine-plugin/src/lib/session-timeline-prior.js +42 -50
  26. package/agent-spine-plugin/src/lib/session-timeline-provider.js +0 -3
  27. package/agent-spine-plugin/src/lib/session-timeline-query.js +0 -1
  28. package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +0 -4
  29. package/agent-spine-plugin/src/lib/session-timeline-source.js +0 -3
  30. package/agent-spine-plugin/src/lib/session-timeline-transport.js +0 -8
  31. package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +0 -3
  32. package/agent-spine-plugin/src/lib/session-timeline.js +370 -408
  33. package/agent-spine-plugin/src/lib/source-roots.js +26 -16
  34. package/agent-spine-plugin/src/lib/timeline-user-feedback.js +0 -1
  35. package/bin/core-bootstrap.js +18 -10
  36. package/bin/launcher-mode.js +5 -0
  37. package/bin/launcher-runtime.js +27 -0
  38. package/bin/verify-bundled-agent-sources.cjs +28 -2
  39. package/blun.mjs +1628 -1194
  40. package/bundled-agent-sources.json +192 -155
  41. package/package.json +1 -1
  42. package/standard-skills/translate-native/README.md +1334 -1
  43. package/standard-skills/translate-native/SKILL.md +1 -1
  44. package/standard-skills/translate-native/VERSION +1 -1
  45. package/standard-skills/translate-native/docs/PREMORTEM.md +901 -0
  46. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION.md +208 -41
  47. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_API.md +1319 -77
  48. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_EVIDENCE_HTTP.md +66 -11
  49. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_HTTP_PROVIDER.md +19 -1
  50. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_RECEIPT_VERIFIER_HTTP.md +24 -6
  51. package/standard-skills/translate-native/integrations/commercial_localization_profile.py +23 -0
  52. package/standard-skills/translate-native/integrations/website_localization.py +234 -10
  53. package/standard-skills/translate-native/integrations/website_localization_benchmark.py +39 -3
  54. package/standard-skills/translate-native/integrations/website_localization_cms.py +512 -10
  55. package/standard-skills/translate-native/integrations/website_localization_cms_client.py +116 -0
  56. package/standard-skills/translate-native/integrations/website_localization_cms_receiver.py +101 -11
  57. package/standard-skills/translate-native/integrations/website_localization_cms_receiver_runtime.py +12 -1
  58. package/standard-skills/translate-native/integrations/website_localization_cms_receiver_store.py +186 -24
  59. package/standard-skills/translate-native/integrations/website_localization_cms_source_client.py +64 -1
  60. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery.py +528 -5
  61. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth.py +918 -0
  62. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth_runtime.py +609 -0
  63. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_client.py +885 -0
  64. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_http.py +1244 -0
  65. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_runtime.py +220 -0
  66. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_sidecar_adapter.py +331 -0
  67. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_capabilities_http.py +444 -0
  68. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_client.py +673 -0
  69. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch.py +1317 -0
  70. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_client.py +826 -0
  71. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_http.py +1152 -0
  72. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_openapi.py +896 -0
  73. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_runtime.py +621 -0
  74. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_http.py +1180 -0
  75. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_runtime.py +1479 -0
  76. package/standard-skills/translate-native/integrations/website_localization_cms_source_http.py +133 -17
  77. package/standard-skills/translate-native/integrations/website_localization_cms_source_runtime.py +319 -4
  78. package/standard-skills/translate-native/integrations/website_localization_cms_source_service.py +72 -2
  79. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_http.py +13 -0
  80. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver.py +56 -13
  81. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_openapi.py +335 -0
  82. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_runtime.py +66 -1
  83. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_processing_monitor.py +73 -7
  84. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_receiver_client.py +93 -9
  85. package/standard-skills/translate-native/integrations/website_localization_evidence_http.py +221 -11
  86. package/standard-skills/translate-native/integrations/website_localization_health.py +6 -0
  87. package/standard-skills/translate-native/integrations/website_localization_http_provider.py +1 -1
  88. package/standard-skills/translate-native/integrations/website_localization_quality_profiles.py +350 -0
  89. package/standard-skills/translate-native/integrations/website_localization_queue.py +126 -56
  90. package/standard-skills/translate-native/integrations/website_localization_receipt_verifier_http.py +166 -13
  91. package/standard-skills/translate-native/integrations/website_localization_release.py +423 -17
  92. package/standard-skills/translate-native/integrations/website_localization_release_coordinator.py +168 -13
  93. package/standard-skills/translate-native/integrations/website_localization_runner.py +10 -0
  94. package/standard-skills/translate-native/integrations/website_localization_worker.py +213 -9
  95. package/standard-skills/translate-native/provenance.json +2 -2
  96. package/standard-skills/translate-native/references/commercial-localization.md +96 -17
  97. package/standard-skills/translate-native/scripts/blun_language_guard.py +3 -1
  98. package/standard-skills/translate-native/scripts/check_commercial_review.py +69 -9
  99. package/standard-skills/translate-native/scripts/commercial_localization_profile.py +829 -31
  100. package/standard-tools/language-guard/VERSION +1 -1
  101. package/standard-tools/language-guard/blun_language_guard.py +3 -1
  102. package/standard-tools/language-guard/check_commercial_review.py +69 -9
  103. package/standard-tools/language-guard/commercial_localization_profile.py +829 -31
  104. package/standard-tools/language-guard/provenance.json +2 -2
  105. package/standard-tools/manifest.json +1 -1
  106. package/worker-host.mjs +1951 -1585
  107. package/agent-spine-plugin/scripts/check-install-king.js +0 -37
package/blun.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:25c271186c418490335e30ef395705d13d90d816c66cac47bb57835f2482f093
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,
@@ -230341,7 +230344,7 @@ var init_full = __esmMin((() => {
230341
230344
  this.agent.log.error("failed to refresh system prompt after compaction", { error });
230342
230345
  }
230343
230346
  await this.agent.injection.injectAfterCompaction();
230344
- this.fitRetainedInput();
230347
+ this.fitRetainedInput({}, result.keptHeadUserMessageCount !== void 0);
230345
230348
  this.lastCompactedState = {
230346
230349
  tokenCount: this.tokenCountWithPending,
230347
230350
  modelAlias: this.agent.activeResponderModel ?? this.agent.config.modelAlias,
@@ -230377,17 +230380,18 @@ var init_full = __esmMin((() => {
230377
230380
  this.agent.turn.onCompactionFinished();
230378
230381
  }
230379
230382
  }
230380
- fitRetainedInput(context = {}) {
230383
+ fitRetainedInput(context = {}, freshBudgetedSelection = false) {
230381
230384
  const budget = this.getCompactionBudgetTokens();
230382
230385
  const window = this.getEffectiveMaxContextTokens();
230383
230386
  const limit = window > 0 ? window - 1 : Infinity;
230387
+ const hardContextLimit = window > 0 && !freshBudgetedSelection ? window : void 0;
230384
230388
  const estimate = (messages) => this.estimateProjectedRequestTokens({
230385
230389
  ...context,
230386
230390
  messages
230387
230391
  });
230388
230392
  const preserveLatestUser = this.agent.turn.hasActiveTurn;
230389
- if (budget === void 0) return this.agent.context.fitCompactionPrefix(limit, estimate, preserveLatestUser);
230390
- return this.agent.context.fitCompactionPrefix(Math.min(Math.floor(budget * .8), limit), estimate, preserveLatestUser) || this.agent.context.fitCompactionPrefix(Math.min(Math.floor(budget * .9) - 1, limit), estimate, preserveLatestUser);
230393
+ if (budget === void 0) return this.agent.context.fitCompactionPrefix(limit, estimate, preserveLatestUser, hardContextLimit);
230394
+ return this.agent.context.fitCompactionPrefix(Math.min(Math.floor(budget * .8), limit), estimate, preserveLatestUser, hardContextLimit) || this.agent.context.fitCompactionPrefix(Math.min(Math.floor(budget * .9) - 1, limit), estimate, preserveLatestUser, hardContextLimit);
230391
230395
  }
230392
230396
  buildInstruction(customInstruction) {
230393
230397
  return renderPrompt(compaction_instruction_default, { customInstruction: customInstruction?.trim() ?? "" }).trimEnd();
@@ -234338,7 +234342,7 @@ var init_context$2 = __esmMin((() => {
234338
234342
  this.agent.microCompaction.reset();
234339
234343
  this.agent.emitStatusUpdated();
234340
234344
  }
234341
- fitCompactionPrefix(maxRequestTokens, estimateRequest, preserveLatestUser = false) {
234345
+ fitCompactionPrefix(maxRequestTokens, estimateRequest, preserveLatestUser = false, hardContextLimit) {
234342
234346
  const prefixLength = this._history.findIndex((message) => message.origin?.kind === "compaction_summary");
234343
234347
  if (prefixLength <= 0 || !Number.isSafeInteger(maxRequestTokens) || maxRequestTokens <= 0) return false;
234344
234348
  const suffix = this._history.slice(prefixLength);
@@ -234350,7 +234354,9 @@ var init_context$2 = __esmMin((() => {
234350
234354
  ];
234351
234355
  return Math.max(estimateTokensForMessages(messages), estimateRequest(project(messages, { dropOrphanResults: true })));
234352
234356
  };
234353
- if (estimate(this._history.slice(0, prefixLength)) <= maxRequestTokens) return false;
234357
+ const requestTokens = estimate(this._history.slice(0, prefixLength));
234358
+ if (requestTokens <= maxRequestTokens) return false;
234359
+ if (hardContextLimit !== void 0 && requestTokens >= hardContextLimit) return false;
234354
234360
  const sourceKey = Symbol("compaction source");
234355
234361
  const sources = collectCompactableUserMessages(this._history.slice(0, prefixLength)).map((message) => ({
234356
234362
  ...message,
@@ -235212,6 +235218,9 @@ var init_engine = __esmMin((() => {
235212
235218
  for (const [event, hooks] of this.byEvent.entries()) result[event] = hooks.length;
235213
235219
  return result;
235214
235220
  }
235221
+ hasMatchingHook(event, matcherValue) {
235222
+ return this.matchingHooks(event, matcherValue).length > 0;
235223
+ }
235215
235224
  closeAdmission() {
235216
235225
  this.admissionClosed = true;
235217
235226
  }
@@ -264964,6 +264973,1381 @@ var init_tool_result_budget = __esmMin((() => {
264964
264973
  TOOL_RESULT_PREVIEW_CHARS = 2e3;
264965
264974
  }));
264966
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
264967
266351
  //#region ../../packages/agent-core/src/agent/response-review-decision.ts
264968
266352
  /** Never carry provider/guard prose, signed receipts or arbitrary codes into logs. */
264969
266353
  function rejectionFindings(report) {
@@ -265018,6 +266402,71 @@ var init_response_review_decision = __esmMin((() => {
265018
266402
  ]);
265019
266403
  }));
265020
266404
  //#endregion
266405
+ //#region ../../packages/agent-core/src/agent/response-review-deadline.ts
266406
+ function createResponseReviewDeadline(source) {
266407
+ const absoluteExpiresAt = performance.now() + ABSOLUTE_TIMEOUT_MS;
266408
+ const absolute = createDeadlineAbortSignal(source, ABSOLUTE_TIMEOUT_MS);
266409
+ const controller = new AbortController();
266410
+ const unlink = linkAbortSignal(absolute.signal, controller);
266411
+ let stage = "setup";
266412
+ let didTimeout = false;
266413
+ let closed = false;
266414
+ let idle;
266415
+ let idleExpiresAt = 0;
266416
+ const expire = () => {
266417
+ didTimeout = true;
266418
+ controller.abort(abortError$1());
266419
+ };
266420
+ const expireIfDue = () => {
266421
+ if (closed || controller.signal.aborted) return;
266422
+ const now = performance.now();
266423
+ if (now >= absoluteExpiresAt || now >= idleExpiresAt) expire();
266424
+ };
266425
+ const refresh = (timeoutMs) => {
266426
+ idleExpiresAt = performance.now() + timeoutMs;
266427
+ if (idle !== void 0) clearTimeout(idle);
266428
+ idle = setTimeout(expire, timeoutMs);
266429
+ };
266430
+ refresh(IDLE_TIMEOUT_MS);
266431
+ return {
266432
+ signal: controller.signal,
266433
+ timedOut: () => didTimeout || absolute.timedOut(),
266434
+ throwIfAborted: () => {
266435
+ expireIfDue();
266436
+ controller.signal.throwIfAborted();
266437
+ },
266438
+ progress: (event) => {
266439
+ expireIfDue();
266440
+ if (closed || controller.signal.aborted) return;
266441
+ if (event === "native.start" && stage === "setup") {
266442
+ stage = "native";
266443
+ refresh(NATIVE_FIRST_CONTENT_MS);
266444
+ } else if (event === "native.text" && stage === "native") refresh(IDLE_TIMEOUT_MS);
266445
+ else if (event === "release.start" && stage === "native") {
266446
+ stage = "release";
266447
+ refresh(IDLE_TIMEOUT_MS);
266448
+ } else if (event === "verification.start" && stage === "release") {
266449
+ stage = "verification";
266450
+ refresh(IDLE_TIMEOUT_MS);
266451
+ }
266452
+ },
266453
+ clear: () => {
266454
+ closed = true;
266455
+ if (idle !== void 0) clearTimeout(idle);
266456
+ idle = void 0;
266457
+ unlink();
266458
+ absolute.clear();
266459
+ }
266460
+ };
266461
+ }
266462
+ var IDLE_TIMEOUT_MS, NATIVE_FIRST_CONTENT_MS, ABSOLUTE_TIMEOUT_MS;
266463
+ var init_response_review_deadline = __esmMin((() => {
266464
+ init_abort();
266465
+ IDLE_TIMEOUT_MS = 3e4;
266466
+ NATIVE_FIRST_CONTENT_MS = 9e4;
266467
+ ABSOLUTE_TIMEOUT_MS = 12e4;
266468
+ }));
266469
+ //#endregion
265021
266470
  //#region ../../packages/agent-core/src/agent/response-review.ts
265022
266471
  function createReviewedOutputDispatcher(options) {
265023
266472
  let pending = [];
@@ -265103,6 +266552,7 @@ function createReviewedOutputDispatcher(options) {
265103
266552
  if (pending.length === 0) return options.dispatch(event);
265104
266553
  if (event.uuid !== stepUuid) throw new Error("Response review step mismatch");
265105
266554
  const currentEpoch = epoch;
266555
+ const isCurrentCandidate = () => epoch === currentEpoch;
265106
266556
  const parts = [...pending];
265107
266557
  reviewing = true;
265108
266558
  const text = parts.map((part) => part.part.type === "text" ? part.part.text : "").join("");
@@ -265116,8 +266566,9 @@ function createReviewedOutputDispatcher(options) {
265116
266566
  for (let attempt = 1; attempt <= MAX_REVIEW_ATTEMPTS; attempt += 1) {
265117
266567
  options.signal.throwIfAborted();
265118
266568
  if (epoch !== currentEpoch) throw new Error("Response review candidate changed during verification");
265119
- const deadline = createDeadlineAbortSignal(options.signal, REVIEW_TIMEOUT_MS);
266569
+ const deadline = createResponseReviewDeadline(options.signal);
265120
266570
  let requested;
266571
+ const pendingOperations = [];
265121
266572
  let attemptRejection;
265122
266573
  let attemptNativeRejection = false;
265123
266574
  try {
@@ -265126,6 +266577,12 @@ function createReviewedOutputDispatcher(options) {
265126
266577
  turnId: event.turnId,
265127
266578
  step: event.step,
265128
266579
  signal: deadline.signal,
266580
+ onProgress: (progress) => {
266581
+ if (isCurrentCandidate()) deadline.progress(progress);
266582
+ },
266583
+ onPendingOperation: (operation) => {
266584
+ pendingOperations.push(operation.then(() => void 0, () => void 0));
266585
+ },
265129
266586
  onRejected: (decision) => {
265130
266587
  if (!deadline.signal.aborted) {
265131
266588
  const message = rejectionMessage(decision);
@@ -265135,13 +266592,14 @@ function createReviewedOutputDispatcher(options) {
265135
266592
  }
265136
266593
  });
265137
266594
  approval = await abortable(requested, deadline.signal);
266595
+ deadline.throwIfAborted();
265138
266596
  rejection = attemptRejection;
265139
266597
  nativeRejection = attemptNativeRejection;
265140
266598
  break;
265141
266599
  } catch {
265142
266600
  options.signal.throwIfAborted();
265143
266601
  if (attemptRejection !== void 0) throw new Error(attemptRejection);
265144
- if (deadline.timedOut() && attempt < MAX_REVIEW_ATTEMPTS && requested !== void 0 && epoch === currentEpoch && await waitForCancelledReview(requested)) continue;
266602
+ if (deadline.timedOut() && attempt < MAX_REVIEW_ATTEMPTS && requested !== void 0 && epoch === currentEpoch && await waitForCancelledReview(requested, pendingOperations)) continue;
265145
266603
  throw new Error(deadline.timedOut() ? "Response review timed out" : "Response review failed");
265146
266604
  } finally {
265147
266605
  deadline.clear();
@@ -265178,10 +266636,16 @@ function createReviewedOutputDispatcher(options) {
265178
266636
  if (epoch === currentEpoch) clear();
265179
266637
  }
265180
266638
  }
265181
- async function waitForCancelledReview(requested) {
266639
+ async function waitForCancelledReview(requested, pendingOperations) {
265182
266640
  const deadline = createDeadlineAbortSignal(options.signal, REVIEW_CANCEL_SETTLE_MS);
265183
266641
  try {
265184
266642
  await abortable(requested.then(() => void 0, () => void 0), deadline.signal);
266643
+ let observed = 0;
266644
+ while (observed < pendingOperations.length) {
266645
+ const batch = pendingOperations.slice(observed);
266646
+ observed = pendingOperations.length;
266647
+ await abortable(Promise.all(batch), deadline.signal);
266648
+ }
265185
266649
  options.signal.throwIfAborted();
265186
266650
  return true;
265187
266651
  } catch {
@@ -265193,13 +266657,13 @@ function createReviewedOutputDispatcher(options) {
265193
266657
  }
265194
266658
  return dispatch;
265195
266659
  }
265196
- var MAX_CANDIDATE_BYTES, MAX_CANDIDATE_PARTS, REVIEW_TIMEOUT_MS, REVIEW_CANCEL_SETTLE_MS, MAX_REVIEW_ATTEMPTS;
266660
+ var MAX_CANDIDATE_BYTES, MAX_CANDIDATE_PARTS, REVIEW_CANCEL_SETTLE_MS, MAX_REVIEW_ATTEMPTS;
265197
266661
  var init_response_review = __esmMin((() => {
265198
266662
  init_abort();
265199
266663
  init_response_review_decision();
266664
+ init_response_review_deadline();
265200
266665
  MAX_CANDIDATE_BYTES = 1048576;
265201
266666
  MAX_CANDIDATE_PARTS = 4096;
265202
- REVIEW_TIMEOUT_MS = 3e4;
265203
266667
  REVIEW_CANCEL_SETTLE_MS = 1e3;
265204
266668
  MAX_REVIEW_ATTEMPTS = 2;
265205
266669
  }));
@@ -265528,6 +266992,7 @@ var init_turn = __esmMin((() => {
265528
266992
  init_canonical_args();
265529
266993
  init_tool_dedup();
265530
266994
  init_tool_result_budget();
266995
+ init_historical_tool_projection();
265531
266996
  init_response_review();
265532
266997
  import_turn_tool_performance_policy = require_turn_tool_performance_policy();
265533
266998
  BLUN_LEAN_TOOL_NAMES = new Set([
@@ -265610,8 +267075,10 @@ var init_turn = __esmMin((() => {
265610
267075
  stepFailureByTurn = /* @__PURE__ */ new Map();
265611
267076
  providerRequestFailureByTurn = /* @__PURE__ */ new Map();
265612
267077
  currentStep = 0;
267078
+ historicalToolProjection;
265613
267079
  constructor(agent) {
265614
267080
  this.agent = agent;
267081
+ this.historicalToolProjection = new HistoricalToolProjection(agent);
265615
267082
  }
265616
267083
  /** Explicit host shutdown only; ordinary cancellation remains resumable. */
265617
267084
  closeAdmission() {
@@ -266017,7 +267484,10 @@ var init_turn = __esmMin((() => {
266017
267484
  turnId,
266018
267485
  origin
266019
267486
  });
267487
+ const previousLastMessage = this.agent.context.history.at(-1);
266020
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;
266021
267491
  const startedAt = Date.now();
266022
267492
  let ended;
266023
267493
  let blockedByUserPromptHook = false;
@@ -266044,7 +267514,7 @@ var init_turn = __esmMin((() => {
266044
267514
  this.agent.log.warn("Turn-start observation failed");
266045
267515
  }
266046
267516
  signal.throwIfAborted();
266047
- const stopReason = await this.runStepLoop(turnId, signal, input, origin, (response) => {
267517
+ const stopReason = await this.runStepLoop(turnId, signal, input, origin, turnAnchor, (response) => {
266048
267518
  completedResponse = response;
266049
267519
  });
266050
267520
  completedStopReason = stopReason;
@@ -266209,7 +267679,7 @@ var init_turn = __esmMin((() => {
266209
267679
  content: hookResult.message
266210
267680
  });
266211
267681
  }
266212
- async runStepLoop(turnId, signal, input, origin, onFinalResponse) {
267682
+ async runStepLoop(turnId, signal, input, origin, turnAnchor, onFinalResponse) {
266213
267683
  let stopHookContinuationUsed = false;
266214
267684
  let goalOutcomeMessageContinuationUsed = false;
266215
267685
  const deduper = new ToolCallDeduplicator({ telemetry: this.agent.telemetry });
@@ -266222,11 +267692,25 @@ var init_turn = __esmMin((() => {
266222
267692
  let taskToolsNeeded = turnNeedsThinking || blunTurnHasAttachment(input);
266223
267693
  const greetingAnchor = this.agent.context.history.findLast((message) => message.role === "user" && message.origin?.kind === "user");
266224
267694
  let partialContext = false;
266225
- const turnHistory = () => {
266226
- 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];
266227
267698
  const selected = taskToolsNeeded ? history : greetingHistory(history, greetingAnchor);
266228
- partialContext = selected.length < history.length;
266229
- 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;
266230
267714
  };
266231
267715
  const turnLLM = this.agent.llmForTurn(turnNeedsThinking ? void 0 : "off");
266232
267716
  const turnTools = this.agent.tools.captureTurnTools();
@@ -266262,8 +267746,8 @@ var init_turn = __esmMin((() => {
266262
267746
  }, completion);
266263
267747
  },
266264
267748
  llm: turnLLM,
266265
- buildMessages: () => this.agent.context.project(turnHistory(), { dropOrphanResults: true }),
266266
- 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), {
266267
267751
  synthesizeMissing: true,
266268
267752
  dropOrphanResults: true,
266269
267753
  dedupeDuplicateToolCalls: true,
@@ -269318,12 +270802,14 @@ function readPayload(result, includeError = false) {
269318
270802
  /** Receipts never enter ordinary tool execution, hooks, replay or model history. */
269319
270803
  function createGuardResponseReviewer(host) {
269320
270804
  const { language, guardVersion } = host;
269321
- return async ({ text, turnId, step, signal, onRejected }) => {
270805
+ return async ({ text, turnId, step, signal, onRejected, onPendingOperation, onProgress }) => {
269322
270806
  const input = {
269323
270807
  text,
269324
270808
  turnId,
269325
270809
  step,
269326
- signal
270810
+ signal,
270811
+ onPendingOperation,
270812
+ onProgress
269327
270813
  };
269328
270814
  const targetSha256 = digest(text);
269329
270815
  let stage = "setup";
@@ -269350,6 +270836,10 @@ function createGuardResponseReviewer(host) {
269350
270836
  const blocked = (reason, findings) => {
269351
270837
  record(signal.aborted ? "cancelled" : reason, findings);
269352
270838
  };
270839
+ const track = (operation) => {
270840
+ onPendingOperation?.(operation.then(() => void 0, () => void 0));
270841
+ return operation;
270842
+ };
269353
270843
  try {
269354
270844
  if (!/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(language) || /^(auto|all)$/i.test(language) || guardVersion.trim().length === 0) return blocked("invalid_settings");
269355
270845
  const resolve = () => {
@@ -269362,13 +270852,14 @@ function createGuardResponseReviewer(host) {
269362
270852
  const current = () => !signal.aborted && resolve()?.client === connection.client;
269363
270853
  if (!current()) return blocked("connection_changed");
269364
270854
  stage = "native";
269365
- const native = await abortable(host.reviewNative({ ...input }), signal);
270855
+ onProgress?.("native.start");
270856
+ const native = await abortable(track(host.reviewNative({ ...input })), signal);
269366
270857
  if (!current()) return blocked("connection_changed");
269367
270858
  if (native === void 0) return blocked("native_unavailable");
269368
270859
  if (!passed(native.nativeness) || !passed(native.orthography)) return blocked("native_rejected");
269369
270860
  const authorized = async (name) => {
269370
270861
  if (!current()) return false;
269371
- return passed(await abortable(host.authorize({
270862
+ return passed(await abortable(track(host.authorize({
269372
270863
  toolName: qualifyMcpToolName(SERVER_NAME, name),
269373
270864
  targetSha256,
269374
270865
  language,
@@ -269377,11 +270868,12 @@ function createGuardResponseReviewer(host) {
269377
270868
  turnId,
269378
270869
  step,
269379
270870
  signal
269380
- }), signal)) && current();
270871
+ })), signal)) && current();
269381
270872
  };
269382
270873
  stage = "release";
270874
+ onProgress?.("release.start");
269383
270875
  if (!await authorized("release_response")) return blocked("release_denied");
269384
- const releaseResult = await abortable(connection.client.callTool("release_response", {
270876
+ const releaseResult = await abortable(track(connection.client.callTool("release_response", {
269385
270877
  target_text: text,
269386
270878
  language,
269387
270879
  content_type: "prose",
@@ -269390,7 +270882,7 @@ function createGuardResponseReviewer(host) {
269390
270882
  nativeness: true,
269391
270883
  orthography: true
269392
270884
  }
269393
- }, signal), signal);
270885
+ }, signal)), signal);
269394
270886
  if (!current()) return blocked("connection_changed");
269395
270887
  if (releaseResult.isError) return blocked("release_rejected", rejectionFindings(readPayload(releaseResult, true)));
269396
270888
  const released = readPayload(releaseResult);
@@ -269398,8 +270890,9 @@ function createGuardResponseReviewer(host) {
269398
270890
  if (!current()) return blocked("connection_changed");
269399
270891
  if (status !== "PASS" || release_allowed !== true || blocking === true || typeof release_token !== "string" || release_token.length === 0 || release_token.length > 65536) return blocked("release_rejected", rejectionFindings(released));
269400
270892
  stage = "verification";
270893
+ onProgress?.("verification.start");
269401
270894
  if (!await authorized("verify_release_token")) return blocked("verification_denied");
269402
- const { status: verificationStatus, valid, checks, payload } = readPayload(await abortable(connection.client.callTool("verify_release_token", {
270895
+ const { status: verificationStatus, valid, checks, payload } = readPayload(await abortable(track(connection.client.callTool("verify_release_token", {
269403
270896
  release_token,
269404
270897
  source_text: "",
269405
270898
  target_text: text,
@@ -269407,7 +270900,7 @@ function createGuardResponseReviewer(host) {
269407
270900
  purpose: "response",
269408
270901
  content_type: "prose",
269409
270902
  short_text_reviewed: false
269410
- }, signal), signal)) ?? {};
270903
+ }, signal)), signal)) ?? {};
269411
270904
  if (!current()) return blocked("connection_changed");
269412
270905
  if (verificationStatus !== "PASS" || valid !== true || !isPlainRecord(checks) || !CHECK_NAMES.every((name) => checks[name] === true) || !isPlainRecord(payload)) return blocked("verification_rejected");
269413
270906
  const { v, source_sha256, target_sha256, language: verifiedLanguage, purpose, content_type, short_text_reviewed, exp, iat } = payload;
@@ -269489,6 +270982,7 @@ async function reviewNativeResponse(agent, language, input) {
269489
270982
  });
269490
270983
  };
269491
270984
  const valueType = (value) => value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
270985
+ let formatRetry = false;
269492
270986
  for (let attempt = 1; attempt <= 2; attempt += 1) {
269493
270987
  let receivedBytes = 0;
269494
270988
  let outputLimitExceeded = false;
@@ -269497,21 +270991,27 @@ async function reviewNativeResponse(agent, language, input) {
269497
270991
  };
269498
270992
  const options = {
269499
270993
  signal,
270994
+ onExecution: (completion) => {
270995
+ input.onPendingOperation?.(completion.then(() => void 0, () => void 0));
270996
+ },
269500
270997
  requestLogFields: {
269501
270998
  turnStep: `review:${input.turnId}.${String(input.step)}`,
269502
270999
  attempt: `${attempt}/2`
269503
271000
  }
269504
271001
  };
269505
- 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.`;
269506
271003
  try {
269507
271004
  signal.throwIfAborted();
269508
- const response = await abortable(agent.generate(provider, prompt, [], [createUserMessage(candidateMessage)], { onMessagePart: (part) => {
271005
+ const requested = agent.generate(provider, prompt, [], [createUserMessage(candidateMessage)], { onMessagePart: (part) => {
269509
271006
  if (part.type === "text") receivedBytes += Buffer.byteLength(part.text);
269510
271007
  if (receivedBytes > MAX_REVIEW_RESULT_BYTES) {
269511
271008
  outputLimitExceeded = true;
269512
271009
  throw new Error("Native review output exceeds limit");
269513
271010
  }
269514
- } }, options), signal);
271011
+ if (!signal.aborted && part.type === "text" && part.text.trim().length > 0) input.onProgress?.("native.text");
271012
+ } }, options);
271013
+ input.onPendingOperation?.(requested.then(() => void 0, () => void 0));
271014
+ const response = await abortable(requested, signal);
269515
271015
  if (response.usage !== null) agent.usage.record(modelAlias, response.usage, "turn");
269516
271016
  signal.throwIfAborted();
269517
271017
  if (response.message.toolCalls.length > 0) return invalid("tool_calls");
@@ -269524,7 +271024,10 @@ async function reviewNativeResponse(agent, language, input) {
269524
271024
  parsed = JSON.parse(text);
269525
271025
  } catch {
269526
271026
  record("invalid_json", attempt);
269527
- if (attempt === 1) continue;
271027
+ if (attempt === 1) {
271028
+ formatRetry = true;
271029
+ continue;
271030
+ }
269528
271031
  return;
269529
271032
  }
269530
271033
  if (!isPlainRecord(parsed) || Object.keys(parsed).length !== 2 || typeof parsed["nativeness"] !== "boolean" || typeof parsed["orthography"] !== "boolean") {
@@ -269535,7 +271038,10 @@ async function reviewNativeResponse(agent, language, input) {
269535
271038
  nativenessType: fields ? valueType(fields["nativeness"]) : "missing",
269536
271039
  orthographyType: fields ? valueType(fields["orthography"]) : "missing"
269537
271040
  });
269538
- 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
+ }
269539
271045
  return;
269540
271046
  }
269541
271047
  record(parsed["nativeness"] && parsed["orthography"] ? "approved" : "native_rejected", attempt, {
@@ -269546,7 +271052,12 @@ async function reviewNativeResponse(agent, language, input) {
269546
271052
  nativeness: parsed["nativeness"],
269547
271053
  orthography: parsed["orthography"]
269548
271054
  };
269549
- } 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
+ }
269550
271061
  return invalid(signal.aborted ? "cancelled" : outputLimitExceeded ? "output_limit" : "generation_failed");
269551
271062
  }
269552
271063
  }
@@ -269601,6 +271112,7 @@ var init_response_review_host = __esmMin((() => {
269601
271112
  init_src$4();
269602
271113
  init_abort();
269603
271114
  init_response_review_mcp();
271115
+ init_provider_retry_wait();
269604
271116
  init_canonical_args();
269605
271117
  NATIVE_REVIEW_PROMPT = [
269606
271118
  "You are an independent language reviewer, not the author of the candidate.",
@@ -334128,1138 +335640,6 @@ var init_blun_media = __esmMin((() => {
334128
335640
  };
334129
335641
  }));
334130
335642
  //#endregion
334131
- //#region ../../packages/kaos/src/errors.ts
334132
- var KaosError, KaosFileExistsError, KaosShellNotFoundError;
334133
- var init_errors$1 = __esmMin((() => {
334134
- KaosError = class extends Error {
334135
- constructor(message) {
334136
- super(message);
334137
- this.name = "KaosError";
334138
- }
334139
- };
334140
- KaosFileExistsError = class extends KaosError {
334141
- constructor(message) {
334142
- super(message);
334143
- this.name = "KaosFileExistsError";
334144
- }
334145
- };
334146
- KaosShellNotFoundError = class extends KaosError {
334147
- constructor(message) {
334148
- super(message);
334149
- this.name = "KaosShellNotFoundError";
334150
- }
334151
- };
334152
- }));
334153
- //#endregion
334154
- //#region ../../packages/kaos/src/environment.ts
334155
- /**
334156
- * Environment — cross-platform probe of OS / shell.
334157
- *
334158
- * Detection is a pure function of injected probes (`platform` / `arch` /
334159
- * `release` / `env` / `isFile` / `execFileText`) so the same suite runs
334160
- * identically on any host OS. `detectEnvironmentFromNode()` bundles the Node
334161
- * defaults for production callers.
334162
- *
334163
- * On Windows the probe expects Git Bash (the canonical POSIX shell that
334164
- * ships with Git for Windows). If it cannot be located the function
334165
- * throws `KaosShellNotFoundError`; the SDK layer can wrap that into a
334166
- * user-facing install hint. Set `BLUN_SHELL_PATH` to override.
334167
- */
334168
- function resolveOsKind(platform) {
334169
- switch (platform) {
334170
- case "darwin": return "macOS";
334171
- case "linux": return "Linux";
334172
- case "win32": return "Windows";
334173
- default: return platform;
334174
- }
334175
- }
334176
- async function detectEnvironment(deps) {
334177
- const osKind = resolveOsKind(deps.platform);
334178
- const osArch = deps.arch;
334179
- const osVersion = deps.release;
334180
- if (deps.platform === "win32") return {
334181
- osKind,
334182
- osArch,
334183
- osVersion,
334184
- shellName: "bash",
334185
- shellPath: await locateWindowsGitBash(deps)
334186
- };
334187
- const candidates = [
334188
- "/bin/bash",
334189
- "/usr/bin/bash",
334190
- "/usr/local/bin/bash"
334191
- ];
334192
- let found;
334193
- for (const p of candidates) if (await deps.isFile(p)) {
334194
- found = p;
334195
- break;
334196
- }
334197
- if (found !== void 0) return {
334198
- osKind,
334199
- osArch,
334200
- osVersion,
334201
- shellName: "bash",
334202
- shellPath: found
334203
- };
334204
- return {
334205
- osKind,
334206
- osArch,
334207
- osVersion,
334208
- shellName: "sh",
334209
- shellPath: "/bin/sh"
334210
- };
334211
- }
334212
- async function locateWindowsGitBash(deps) {
334213
- const checked = [];
334214
- const override = deps.env["BLUN_SHELL_PATH"]?.trim();
334215
- if (override !== void 0 && override.length > 0) {
334216
- checked.push(override);
334217
- if (await deps.isFile(override)) return override;
334218
- }
334219
- const gitExecutables = await findExecutablesOnPath("git.exe", deps.env["PATH"], deps.platform, deps.isFile);
334220
- for (const gitExe of gitExecutables) {
334221
- const inferred = gitBashCandidatesFromGitExe(gitExe);
334222
- if (inferred !== void 0) for (const candidate of inferred) {
334223
- checked.push(candidate);
334224
- if (await deps.isFile(candidate)) return candidate;
334225
- }
334226
- const gitExecPath = await readGitExecPath(deps, gitExe);
334227
- if (gitExecPath === void 0) continue;
334228
- for (const candidate of gitBashCandidatesFromGitExecPath(gitExecPath)) {
334229
- checked.push(candidate);
334230
- if (await deps.isFile(candidate)) return candidate;
334231
- }
334232
- }
334233
- const candidates = [
334234
- "C:\\Program Files\\Git\\bin\\bash.exe",
334235
- "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
334236
- "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
334237
- "C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
334238
- ];
334239
- const localAppData = deps.env["LOCALAPPDATA"]?.trim();
334240
- if (localAppData !== void 0 && localAppData.length > 0) {
334241
- candidates.push(`${localAppData}\\Programs\\Git\\bin\\bash.exe`);
334242
- candidates.push(`${localAppData}\\Programs\\Git\\usr\\bin\\bash.exe`);
334243
- }
334244
- for (const candidate of candidates) {
334245
- checked.push(candidate);
334246
- if (await deps.isFile(candidate)) return candidate;
334247
- }
334248
- 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(", ")}.`);
334249
- }
334250
- async function readGitExecPath(deps, gitExe) {
334251
- if (deps.platform === "win32" && !isAbsoluteWindowsPath(gitExe)) return void 0;
334252
- const stdout = await deps.execFileText(gitExe, ["--exec-path"], GIT_EXEC_PATH_TIMEOUT_MS);
334253
- if (stdout === void 0) return void 0;
334254
- for (const line of stdout.split(/\r?\n/)) {
334255
- const execPath = line.trim();
334256
- if (execPath.length > 0) return execPath;
334257
- }
334258
- }
334259
- function gitBashCandidatesFromGitExe(gitExe) {
334260
- const normalizedGitExe = path$18.win32.normalize(normalizeWindowsPath(gitExe));
334261
- const gitDir = path$18.win32.dirname(normalizedGitExe);
334262
- const gitDirName = path$18.win32.basename(gitDir).toLowerCase();
334263
- if (gitDirName !== "cmd" && gitDirName !== "bin") return;
334264
- return gitBashCandidatesFromGitRoot(path$18.win32.dirname(gitDir));
334265
- }
334266
- function gitBashCandidatesFromGitExecPath(execPath) {
334267
- const normalized = path$18.win32.normalize(normalizeWindowsPath(execPath));
334268
- const parts = normalized.split("\\");
334269
- for (let i = parts.length - 1; i >= 0; i -= 1) {
334270
- const segment = parts[i]?.toLowerCase();
334271
- if (segment === "mingw32" || segment === "mingw64") {
334272
- const root = parts.slice(0, i).join("\\");
334273
- if (root.length > 0) return gitBashCandidatesFromGitRoot(root);
334274
- }
334275
- }
334276
- return gitBashCandidatesFromGitRoot(path$18.win32.join(normalized, "..", ".."));
334277
- }
334278
- function gitBashCandidatesFromGitRoot(root) {
334279
- 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"))];
334280
- }
334281
- function normalizeWindowsPath(path) {
334282
- return path.replaceAll("/", "\\");
334283
- }
334284
- function isAbsoluteWindowsPath(path) {
334285
- return path$18.win32.isAbsolute(normalizeWindowsPath(path));
334286
- }
334287
- function dedupeWindowsPaths(paths) {
334288
- const deduped = [];
334289
- const seen = /* @__PURE__ */ new Set();
334290
- for (const path of paths) {
334291
- const key = normalizeWindowsPath(path).toLowerCase();
334292
- if (seen.has(key)) continue;
334293
- seen.add(key);
334294
- deduped.push(path);
334295
- }
334296
- return deduped;
334297
- }
334298
- function detectEnvironmentFromNode() {
334299
- if (detectedEnvironment !== void 0) return detectedEnvironment;
334300
- const platform = process.platform;
334301
- const env = process.env;
334302
- const isFile = async (path) => {
334303
- try {
334304
- await access(path, constants.F_OK);
334305
- return true;
334306
- } catch {
334307
- return false;
334308
- }
334309
- };
334310
- detectedEnvironment = detectEnvironment({
334311
- platform,
334312
- arch: process.arch,
334313
- release: nodeOs.release(),
334314
- env,
334315
- isFile,
334316
- execFileText: execFileText$1
334317
- });
334318
- return detectedEnvironment;
334319
- }
334320
- async function findExecutablesOnPath(name, pathEnv, platform, isFile) {
334321
- if (pathEnv === void 0 || pathEnv.length === 0) return [];
334322
- const listSep = platform === "win32" ? ";" : ":";
334323
- const dirSep = platform === "win32" ? "\\" : "/";
334324
- const paths = [];
334325
- for (const rawDir of pathEnv.split(listSep)) {
334326
- const dir = rawDir.trim();
334327
- if (dir.length === 0) continue;
334328
- if (platform === "win32" && !isAbsoluteWindowsPath(dir)) continue;
334329
- const candidate = dir.endsWith(dirSep) ? `${dir}${name}` : `${dir}${dirSep}${name}`;
334330
- if (await isFile(candidate)) paths.push(candidate);
334331
- }
334332
- return platform === "win32" ? dedupeWindowsPaths(paths) : paths;
334333
- }
334334
- async function execFileText$1(file, args, timeoutMs) {
334335
- return new Promise((resolve) => {
334336
- execFile(file, [...args], {
334337
- encoding: "utf8",
334338
- timeout: timeoutMs,
334339
- windowsHide: true
334340
- }, (error, stdout) => {
334341
- if (error !== null) {
334342
- resolve(void 0);
334343
- return;
334344
- }
334345
- resolve(stdout);
334346
- });
334347
- });
334348
- }
334349
- var GIT_EXEC_PATH_TIMEOUT_MS, detectedEnvironment;
334350
- var init_environment$1 = __esmMin((() => {
334351
- init_errors$1();
334352
- GIT_EXEC_PATH_TIMEOUT_MS = 5e3;
334353
- }));
334354
- //#endregion
334355
- //#region ../../packages/kaos/src/internal.ts
334356
- function isUtf8Continuation(byte) {
334357
- return byte >= 128 && byte <= 191;
334358
- }
334359
- function decodeUtf8Ignore(data) {
334360
- let output = "";
334361
- let i = 0;
334362
- while (i < data.length) {
334363
- const b0 = data[i];
334364
- if (b0 === void 0) break;
334365
- if (b0 <= 127) {
334366
- output += String.fromCodePoint(b0);
334367
- i += 1;
334368
- continue;
334369
- }
334370
- if (b0 >= 194 && b0 <= 223) {
334371
- const b1 = data[i + 1];
334372
- if (b1 !== void 0 && isUtf8Continuation(b1)) {
334373
- output += String.fromCodePoint((b0 & 31) << 6 | b1 & 63);
334374
- i += 2;
334375
- continue;
334376
- }
334377
- i += 1;
334378
- continue;
334379
- }
334380
- if (b0 >= 224 && b0 <= 239) {
334381
- const b1 = data[i + 1];
334382
- const b2 = data[i + 2];
334383
- 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)) {
334384
- output += String.fromCodePoint((b0 & 15) << 12 | (b1 & 63) << 6 | b2 & 63);
334385
- i += 3;
334386
- continue;
334387
- }
334388
- i += 1;
334389
- continue;
334390
- }
334391
- if (b0 >= 240 && b0 <= 244) {
334392
- const b1 = data[i + 1];
334393
- const b2 = data[i + 2];
334394
- const b3 = data[i + 3];
334395
- 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)) {
334396
- output += String.fromCodePoint((b0 & 7) << 18 | (b1 & 63) << 12 | (b2 & 63) << 6 | b3 & 63);
334397
- i += 4;
334398
- continue;
334399
- }
334400
- i += 1;
334401
- continue;
334402
- }
334403
- i += 1;
334404
- }
334405
- return output;
334406
- }
334407
- function decodeUtf16LeIgnore(data) {
334408
- let output = "";
334409
- let i = 0;
334410
- while (i + 1 < data.length) {
334411
- const first = data[i];
334412
- const second = data[i + 1];
334413
- if (first === void 0 || second === void 0) break;
334414
- const codeUnit = first | second << 8;
334415
- if (codeUnit >= 55296 && codeUnit <= 56319) {
334416
- const lowFirst = data[i + 2];
334417
- const lowSecond = data[i + 3];
334418
- if (lowFirst !== void 0 && lowSecond !== void 0) {
334419
- const low = lowFirst | lowSecond << 8;
334420
- if (low >= 56320 && low <= 57343) {
334421
- const codePoint = 65536 + (codeUnit - 55296 << 10) + (low - 56320);
334422
- output += String.fromCodePoint(codePoint);
334423
- i += 4;
334424
- continue;
334425
- }
334426
- }
334427
- i += 2;
334428
- continue;
334429
- }
334430
- if (codeUnit >= 56320 && codeUnit <= 57343) {
334431
- i += 2;
334432
- continue;
334433
- }
334434
- output += String.fromCodePoint(codeUnit);
334435
- i += 2;
334436
- }
334437
- return output;
334438
- }
334439
- /**
334440
- * Decode a Buffer into a string with Python-compatible `errors` handling.
334441
- *
334442
- * - `'strict'` (default): throw on invalid sequences (via TextDecoder `fatal: true`)
334443
- * - `'replace'`: substitute each invalid sequence with U+FFFD (TextDecoder default)
334444
- * - `'ignore'`: drop invalid input sequences while preserving valid U+FFFD characters
334445
- *
334446
- * Falls back to `Buffer.toString(encoding)` for encodings TextDecoder does not
334447
- * support (e.g. `hex`, `base64`, `binary`, `latin1`) — those are lossless
334448
- * byte-to-character mappings so `errors` has no effect.
334449
- * @internal
334450
- */
334451
- function decodeTextWithErrors(data, encoding, errors = "strict", ignoreBOM = false) {
334452
- let webLabel;
334453
- switch (encoding) {
334454
- case "utf-8":
334455
- case "utf8":
334456
- webLabel = "utf-8";
334457
- break;
334458
- case "utf16le":
334459
- case "ucs2":
334460
- case "ucs-2":
334461
- webLabel = "utf-16le";
334462
- break;
334463
- default: webLabel = void 0;
334464
- }
334465
- if (webLabel === void 0) return data.toString(encoding);
334466
- if (errors === "strict") return new TextDecoder(webLabel, {
334467
- fatal: true,
334468
- ignoreBOM
334469
- }).decode(data);
334470
- if (errors === "ignore") return webLabel === "utf-8" ? decodeUtf8Ignore(data) : decodeUtf16LeIgnore(data);
334471
- return new TextDecoder(webLabel, {
334472
- fatal: false,
334473
- ignoreBOM
334474
- }).decode(data);
334475
- }
334476
- /**
334477
- * Convert a glob pattern segment (e.g. "*.txt", "file?.log") into a RegExp.
334478
- * Mirrors Python pathlib behavior: includes dotfiles, case-sensitive by default.
334479
- * @internal
334480
- */
334481
- function globPatternToRegex(pattern, caseSensitive) {
334482
- let regex = "^";
334483
- for (let i = 0; i < pattern.length; i++) {
334484
- const ch = pattern[i];
334485
- if (ch === void 0) break;
334486
- switch (ch) {
334487
- case "*":
334488
- regex += "[^/]*";
334489
- break;
334490
- case "?":
334491
- regex += "[^/]";
334492
- break;
334493
- case "[": {
334494
- const end = pattern.indexOf("]", i + 1);
334495
- if (end === -1) regex += "\\[";
334496
- else {
334497
- let charClass = pattern.slice(i + 1, end);
334498
- charClass = charClass.replace(/\\/g, "\\\\");
334499
- if (charClass.startsWith("!")) charClass = "^" + charClass.slice(1);
334500
- else if (charClass.startsWith("^")) charClass = "\\" + charClass;
334501
- regex += "[" + charClass + "]";
334502
- i = end;
334503
- }
334504
- break;
334505
- }
334506
- case "\\":
334507
- if (i + 1 < pattern.length) {
334508
- const next = pattern.charAt(i + 1);
334509
- regex += next.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
334510
- i++;
334511
- } else regex += "\\\\";
334512
- break;
334513
- default: regex += ch.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
334514
- }
334515
- }
334516
- regex += "$";
334517
- return new RegExp(regex, caseSensitive ? "" : "i");
334518
- }
334519
- var BufferedReadable;
334520
- var init_internal = __esmMin((() => {
334521
- BufferedReadable = class extends Readable$1 {
334522
- _source;
334523
- _ended = false;
334524
- constructor(source) {
334525
- super({ highWaterMark: 128 * 1024 });
334526
- this._source = source;
334527
- this._source.on("data", this._onData);
334528
- this._source.on("end", this._onEnd);
334529
- this._source.on("close", this._onClose);
334530
- this._source.on("error", this._onError);
334531
- }
334532
- _read() {
334533
- if (!this._ended && !this.destroyed) this._source.resume();
334534
- }
334535
- _destroy(error, callback) {
334536
- this._source.off("data", this._onData);
334537
- this._source.off("end", this._onEnd);
334538
- this._source.off("close", this._onClose);
334539
- this._source.off("error", this._onError);
334540
- this._source.destroy();
334541
- callback(error);
334542
- }
334543
- _onData = (chunk) => {
334544
- if (!this.push(chunk)) this._source.pause();
334545
- };
334546
- _onEnd = () => {
334547
- this._ended = true;
334548
- this.push(null);
334549
- };
334550
- _onClose = () => {
334551
- if (!this._ended) {
334552
- this._ended = true;
334553
- this.push(null);
334554
- }
334555
- };
334556
- _onError = (error) => {
334557
- this.destroy(error);
334558
- };
334559
- };
334560
- }));
334561
- //#endregion
334562
- //#region ../../packages/kaos/src/login-shell-path.ts
334563
- /**
334564
- * Login-shell PATH probe — enrich `process.env.PATH` with entries from the
334565
- * user's login shell.
334566
- *
334567
- * When blun is launched from a context that skipped the user's shell
334568
- * profile (GUI launchers, non-login parent shells), `process.env.PATH`
334569
- * misses entries like `/opt/homebrew/bin`, so commands spawned by the Bash
334570
- * tool can't find tools the user has in their interactive shell (e.g.
334571
- * `gh`). We run the user's login shell once (`$SHELL -l -c /usr/bin/env`),
334572
- * extract its PATH, and append the entries the current PATH lacks. Existing
334573
- * entries keep their order and priority; failures (no resolvable shell,
334574
- * hung or broken profile) silently leave PATH untouched.
334575
- *
334576
- * launchd/daemon launches can leave `$SHELL` unset or blank (see
334577
- * `defaultShell()` in agent-core's terminalService for the same case), so
334578
- * the probe falls back to the OS account's login shell from the user
334579
- * database before giving up.
334580
- *
334581
- * Like `detectEnvironment`, the probe is a pure function of injected deps
334582
- * so the suite runs identically on any host. Windows is skipped: the
334583
- * problem is specific to POSIX login-shell profiles.
334584
- */
334585
- /**
334586
- * Run the user's login shell and return its PATH, or `undefined` when the
334587
- * probe does not apply (Windows, no resolvable shell) or fails (spawn
334588
- * error, timeout, no PATH in the output).
334589
- */
334590
- async function probeLoginShellPath(deps) {
334591
- if (deps.platform === "win32") return void 0;
334592
- const envShell = deps.env["SHELL"]?.trim();
334593
- const shell = envShell === void 0 || envShell.length === 0 ? deps.userShell() : envShell;
334594
- if (shell === void 0 || shell.length === 0) return void 0;
334595
- const stdout = await deps.execFileText(shell, [
334596
- "-l",
334597
- "-c",
334598
- "/usr/bin/env"
334599
- ], LOGIN_SHELL_ENV_TIMEOUT_MS);
334600
- if (stdout === void 0) return void 0;
334601
- let path;
334602
- for (const line of stdout.split("\n")) if (line.startsWith("PATH=")) path = line.slice(5).trim();
334603
- if (path === void 0 || path.length === 0) return void 0;
334604
- return path;
334605
- }
334606
- /**
334607
- * Union of the current PATH and the login-shell PATH: the current PATH
334608
- * string is kept verbatim — including empty components, which POSIX
334609
- * command lookup treats as the current directory — and login-shell
334610
- * entries the current PATH lacks are appended in their own order. When
334611
- * nothing is missing the current string is returned unchanged. Only
334612
- * absolute login-shell entries are imported: empty, `.`, and relative
334613
- * components are all cwd-dependent lookup, and appending one the user
334614
- * did not already have would widen their search path — LocalKaos runs
334615
- * commands from arbitrary workspace directories.
334616
- */
334617
- function mergeLoginShellPath(currentPath, loginShellPath) {
334618
- const current = currentPath ?? "";
334619
- const seen = new Set(current.split(":").filter((entry) => entry.length > 0));
334620
- const additions = [];
334621
- for (const entry of loginShellPath.split(":")) {
334622
- if (!entry.startsWith("/") || seen.has(entry)) continue;
334623
- seen.add(entry);
334624
- additions.push(entry);
334625
- }
334626
- if (additions.length === 0) return current;
334627
- if (currentPath === void 0) return additions.join(":");
334628
- return `${current}:${additions.join(":")}`;
334629
- }
334630
- /** Probe the login shell and merge its PATH into `deps.env['PATH']`. */
334631
- async function applyLoginShellPath(deps) {
334632
- const loginShellPath = await probeLoginShellPath(deps);
334633
- if (loginShellPath === void 0) return;
334634
- const currentPath = deps.env["PATH"];
334635
- const merged = mergeLoginShellPath(currentPath, loginShellPath);
334636
- if (merged === (currentPath ?? "")) return;
334637
- deps.env["PATH"] = merged;
334638
- }
334639
- /**
334640
- * Production convenience — apply the probe to `process.env` once per
334641
- * process. Memoised like `detectEnvironmentFromNode`: the login-shell PATH
334642
- * does not change for the lifetime of the process, and repeated
334643
- * `LocalKaos.create()` calls must not re-spawn the shell.
334644
- */
334645
- /**
334646
- * Login shell from the OS user database (`/etc/passwd` via getpwuid on
334647
- * Linux, Directory Services on macOS). `userInfo()` throws when the uid
334648
- * has no database entry (e.g. containers running an arbitrary uid), and
334649
- * service accounts may carry `/usr/sbin/nologin` — the latter needs no
334650
- * special casing here because probing it simply fails and degrades
334651
- * silently.
334652
- */
334653
- function userShellFromNode() {
334654
- try {
334655
- const shell = userInfo().shell;
334656
- return shell === null || shell.length === 0 ? void 0 : shell;
334657
- } catch {
334658
- return;
334659
- }
334660
- }
334661
- function applyLoginShellPathFromNode() {
334662
- if (appliedLoginShellPath !== void 0) return appliedLoginShellPath;
334663
- appliedLoginShellPath = applyLoginShellPath({
334664
- platform: process.platform,
334665
- env: process.env,
334666
- userShell: userShellFromNode,
334667
- execFileText: execFileText$1
334668
- });
334669
- return appliedLoginShellPath;
334670
- }
334671
- var LOGIN_SHELL_ENV_TIMEOUT_MS, appliedLoginShellPath;
334672
- var init_login_shell_path = __esmMin((() => {
334673
- init_environment$1();
334674
- LOGIN_SHELL_ENV_TIMEOUT_MS = 5e3;
334675
- }));
334676
- //#endregion
334677
- //#region ../../packages/kaos/src/local.ts
334678
- /**
334679
- * Build the `(dev, ino)` cycle-detection key used by `_globWalk`'s
334680
- * visited set. Returns `null` when `ino` is 0, which Node returns on
334681
- * filesystems that don't carry inodes (Windows FAT/exFAT, some SMB/NFS
334682
- * mounts). A null key signals "no reliable identity for this dir" so
334683
- * the caller skips visited tracking for that descent — cycle safety
334684
- * is weakened on those filesystems, but normal walking works instead
334685
- * of every directory colliding on the shared key `"<dev>:0"`.
334686
- */
334687
- function cycleKey(s) {
334688
- if (s.ino === 0) return null;
334689
- return `${String(s.dev)}:${String(s.ino)}`;
334690
- }
334691
- function buildLocalSpawnOptions(_isWindows, cwd, env) {
334692
- return {
334693
- cwd,
334694
- env,
334695
- stdio: [
334696
- "pipe",
334697
- "pipe",
334698
- "pipe"
334699
- ],
334700
- detached: true,
334701
- windowsHide: true
334702
- };
334703
- }
334704
- function isUtf8Encoding(encoding) {
334705
- return encoding === "utf-8" || encoding === "utf8";
334706
- }
334707
- function* splitLinesKeepingTerminator(text) {
334708
- if (text.length === 0) return;
334709
- let start = 0;
334710
- for (let i = 0; i < text.length; i += 1) if (text.codePointAt(i) === 10) {
334711
- yield text.slice(start, i + 1);
334712
- start = i + 1;
334713
- }
334714
- if (start < text.length) yield text.slice(start);
334715
- }
334716
- function updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr) {
334717
- for (let i = 0; i < chunk.length; i += 1) {
334718
- const byte = chunk[i];
334719
- if (byte === void 0) continue;
334720
- if (byte === 13) {
334721
- if (prevWasCr) flags.hasLoneCr = true;
334722
- prevWasCr = true;
334723
- } else if (byte === 10) {
334724
- if (prevWasCr) flags.hasCrLf = true;
334725
- else flags.hasLf = true;
334726
- prevWasCr = false;
334727
- } else {
334728
- if (prevWasCr) flags.hasLoneCr = true;
334729
- prevWasCr = false;
334730
- }
334731
- }
334732
- return prevWasCr;
334733
- }
334734
- function createUtf8Validator() {
334735
- let needed = 0;
334736
- let lower = 128;
334737
- let upper = 191;
334738
- const fail = () => {
334739
- throw new TypeError("Invalid UTF-8 data");
334740
- };
334741
- return {
334742
- write(chunk) {
334743
- for (let i = 0; i < chunk.length; i += 1) {
334744
- const byte = chunk[i];
334745
- if (byte === void 0) continue;
334746
- if (needed === 0) {
334747
- if (byte <= 127) continue;
334748
- if (byte >= 194 && byte <= 223) needed = 1;
334749
- else if (byte === 224) {
334750
- needed = 2;
334751
- lower = 160;
334752
- } else if (byte >= 225 && byte <= 236) needed = 2;
334753
- else if (byte === 237) {
334754
- needed = 2;
334755
- upper = 159;
334756
- } else if (byte >= 238 && byte <= 239) needed = 2;
334757
- else if (byte === 240) {
334758
- needed = 3;
334759
- lower = 144;
334760
- } else if (byte >= 241 && byte <= 243) needed = 3;
334761
- else if (byte === 244) {
334762
- needed = 3;
334763
- upper = 143;
334764
- } else fail();
334765
- } else {
334766
- if (byte < lower || byte > upper) fail();
334767
- lower = 128;
334768
- upper = 191;
334769
- needed -= 1;
334770
- }
334771
- }
334772
- },
334773
- end() {
334774
- if (needed !== 0) fail();
334775
- }
334776
- };
334777
- }
334778
- async function readRange(fh, start, length) {
334779
- const data = Buffer.alloc(length);
334780
- let offset = 0;
334781
- while (offset < length) {
334782
- const { bytesRead } = await fh.read(data, offset, length - offset, start + offset);
334783
- if (bytesRead === 0) break;
334784
- offset += bytesRead;
334785
- }
334786
- return offset === length ? data : data.subarray(0, offset);
334787
- }
334788
- function waitForSpawn(child) {
334789
- return new Promise((resolve, reject) => {
334790
- const onSpawn = () => {
334791
- child.off("error", onError);
334792
- resolve();
334793
- };
334794
- const onError = (err) => {
334795
- child.off("spawn", onSpawn);
334796
- reject(err);
334797
- };
334798
- child.once("spawn", onSpawn);
334799
- child.once("error", onError);
334800
- });
334801
- }
334802
- var isWindows$1, READ_CHUNK_SIZE, LocalProcess, LocalKaos;
334803
- var init_local = __esmMin((() => {
334804
- init_dist$6();
334805
- init_environment$1();
334806
- init_errors$1();
334807
- init_internal();
334808
- init_login_shell_path();
334809
- isWindows$1 = process.platform === "win32";
334810
- READ_CHUNK_SIZE = 64 * 1024;
334811
- LocalProcess = class {
334812
- stdin;
334813
- stdout;
334814
- stderr;
334815
- pid;
334816
- _child;
334817
- _exitCode = null;
334818
- _exitPromise;
334819
- _disposed = false;
334820
- constructor(child) {
334821
- if (child.stdin === null || child.stdout === null || child.stderr === null) throw new Error("Process must be created with stdin/stdout/stderr pipes.");
334822
- this._child = child;
334823
- this.stdin = child.stdin;
334824
- this.stdout = new BufferedReadable(child.stdout);
334825
- this.stderr = new BufferedReadable(child.stderr);
334826
- this.pid = child.pid ?? -1;
334827
- this._exitPromise = new Promise((resolve, reject) => {
334828
- child.on("exit", (code) => {
334829
- this._exitCode = code ?? -1;
334830
- resolve(this._exitCode);
334831
- });
334832
- child.on("error", (error) => {
334833
- reject(error);
334834
- });
334835
- });
334836
- }
334837
- get exitCode() {
334838
- return this._exitCode;
334839
- }
334840
- async wait() {
334841
- return this._exitPromise;
334842
- }
334843
- kill(signal) {
334844
- if (this.pid <= 0) return Promise.resolve();
334845
- if (isWindows$1) {
334846
- const taskkillArgs = [
334847
- "/T",
334848
- "/F",
334849
- "/PID",
334850
- String(this.pid)
334851
- ];
334852
- return new Promise((resolve) => {
334853
- const killer = spawn("taskkill", taskkillArgs, {
334854
- stdio: "ignore",
334855
- windowsHide: true
334856
- });
334857
- const done = () => {
334858
- resolve();
334859
- };
334860
- killer.once("error", done);
334861
- killer.once("close", done);
334862
- });
334863
- }
334864
- try {
334865
- process.kill(-this.pid, signal ?? "SIGTERM");
334866
- } catch (error) {
334867
- const err = error;
334868
- if (err.code === "ESRCH") return Promise.resolve();
334869
- if (err.code === "EPERM") {
334870
- try {
334871
- this._child.kill(signal ?? "SIGTERM");
334872
- } catch {}
334873
- return Promise.resolve();
334874
- }
334875
- throw error;
334876
- }
334877
- return Promise.resolve();
334878
- }
334879
- dispose() {
334880
- if (this._disposed) return;
334881
- this._disposed = true;
334882
- this.stdin.destroy();
334883
- this.stdout.destroy();
334884
- this.stderr.destroy();
334885
- }
334886
- };
334887
- LocalKaos = class LocalKaos {
334888
- name = "local";
334889
- osEnv;
334890
- _cwd;
334891
- _envLayers;
334892
- constructor(osEnv, cwd, envLayers = []) {
334893
- this._cwd = normalize$4(cwd ?? process.cwd());
334894
- this.osEnv = osEnv;
334895
- this._envLayers = envLayers;
334896
- }
334897
- /**
334898
- * Construct a fresh `LocalKaos` after probing the host environment.
334899
- *
334900
- * Each call returns a new instance with its own `_cwd`; concurrent
334901
- * callers can therefore operate on independent working directories
334902
- * without polluting one another.
334903
- */
334904
- static async create() {
334905
- const [osEnv] = await Promise.all([detectEnvironmentFromNode(), applyLoginShellPathFromNode()]);
334906
- return new LocalKaos(osEnv);
334907
- }
334908
- withCwd(cwd) {
334909
- return new LocalKaos(this.osEnv, cwd, this._envLayers);
334910
- }
334911
- withEnv(env) {
334912
- return new LocalKaos(this.osEnv, this._cwd, [...this._envLayers, env]);
334913
- }
334914
- _resolvePath(path) {
334915
- if (isAbsolute$2(path)) return normalize$4(path);
334916
- return join$4(this._cwd, path);
334917
- }
334918
- pathClass() {
334919
- return isWindows$1 ? "win32" : "posix";
334920
- }
334921
- normpath(path) {
334922
- return normalize$4(path);
334923
- }
334924
- gethome() {
334925
- return normalize$4(homedir());
334926
- }
334927
- getcwd() {
334928
- return this._cwd;
334929
- }
334930
- /**
334931
- * Change the working directory of this LocalKaos instance.
334932
- *
334933
- * Unlike Python's `os.chdir`, this is instance-scoped and never touches
334934
- * `process.cwd()`. Child processes spawned via {@link exec} inherit this
334935
- * instance's `_cwd`; concurrent LocalKaos instances each carry their own
334936
- * independent cwd. If you need Python-compatible process-global cwd,
334937
- * call `process.chdir(x)` directly.
334938
- */
334939
- async chdir(path) {
334940
- const resolved = this._resolvePath(path);
334941
- if (!(await stat(resolved)).isDirectory()) throw new Error(`Not a directory: ${resolved}`);
334942
- this._cwd = resolved;
334943
- }
334944
- async stat(path, options) {
334945
- const resolved = this._resolvePath(path);
334946
- const s = options?.followSymlinks ?? true ? await stat(resolved) : await lstat(resolved);
334947
- return {
334948
- stMode: s.mode,
334949
- stIno: s.ino,
334950
- stDev: s.dev,
334951
- stNlink: s.nlink,
334952
- stUid: s.uid,
334953
- stGid: s.gid,
334954
- stSize: s.size,
334955
- stAtime: s.atimeMs / 1e3,
334956
- stMtime: s.mtimeMs / 1e3,
334957
- stCtime: isWindows$1 ? s.birthtimeMs / 1e3 : s.ctimeMs / 1e3
334958
- };
334959
- }
334960
- async *iterdir(path) {
334961
- const resolved = this._resolvePath(path);
334962
- const entries = await readdir(resolved);
334963
- for (const entry of entries) yield join$4(resolved, entry);
334964
- }
334965
- async *glob(path, pattern, options) {
334966
- const resolved = this._resolvePath(path);
334967
- const caseSensitive = options?.caseSensitive ?? true;
334968
- const patternParts = pattern.split("/");
334969
- const initVisited = /* @__PURE__ */ new Set();
334970
- try {
334971
- const rootKey = cycleKey(await stat(resolved));
334972
- if (rootKey !== null) initVisited.add(rootKey);
334973
- } catch {}
334974
- yield* this._globWalk(resolved, patternParts, caseSensitive, initVisited);
334975
- }
334976
- async *_globWalk(basePath, patternParts, caseSensitive, visited) {
334977
- if (patternParts.length === 0) return;
334978
- const [currentPattern, ...remainingParts] = patternParts;
334979
- if (currentPattern === "**") {
334980
- if (remainingParts.length > 0) yield* this._globWalk(basePath, remainingParts, caseSensitive, visited);
334981
- else yield basePath;
334982
- let entries;
334983
- try {
334984
- entries = await readdir(basePath);
334985
- } catch {
334986
- return;
334987
- }
334988
- for (const entry of entries) {
334989
- const fullPath = join$4(basePath, entry);
334990
- let entryStat;
334991
- try {
334992
- entryStat = await stat(fullPath);
334993
- } catch {
334994
- continue;
334995
- }
334996
- if (entryStat.isDirectory()) {
334997
- const key = cycleKey(entryStat);
334998
- if (key !== null && visited.has(key)) continue;
334999
- yield* this._globWalk(fullPath, patternParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
335000
- } else if (remainingParts.length === 0) yield fullPath;
335001
- }
335002
- } else {
335003
- const regex = globPatternToRegex(currentPattern ?? "", caseSensitive);
335004
- let entries;
335005
- try {
335006
- entries = await readdir(basePath);
335007
- } catch {
335008
- return;
335009
- }
335010
- for (const entry of entries) {
335011
- if (!regex.test(entry)) continue;
335012
- const fullPath = join$4(basePath, entry);
335013
- if (remainingParts.length === 0) yield fullPath;
335014
- else {
335015
- let entryStat;
335016
- try {
335017
- entryStat = await stat(fullPath);
335018
- } catch {
335019
- continue;
335020
- }
335021
- if (entryStat.isDirectory()) {
335022
- const key = cycleKey(entryStat);
335023
- if (key !== null && visited.has(key)) continue;
335024
- yield* this._globWalk(fullPath, remainingParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
335025
- }
335026
- }
335027
- }
335028
- }
335029
- }
335030
- async readBytes(path, n) {
335031
- const resolved = this._resolvePath(path);
335032
- if (n === void 0) return Buffer.from(await readFile(resolved));
335033
- const fh = await open(resolved, "r");
335034
- try {
335035
- const buf = Buffer.alloc(n);
335036
- const { bytesRead } = await fh.read(buf, 0, n, 0);
335037
- return buf.subarray(0, bytesRead);
335038
- } finally {
335039
- await fh.close();
335040
- }
335041
- }
335042
- async readText(path, options) {
335043
- const resolved = this._resolvePath(path);
335044
- const encoding = options?.encoding ?? "utf-8";
335045
- const errors = options?.errors ?? "strict";
335046
- return decodeTextWithErrors(await readFile(resolved), encoding, errors);
335047
- }
335048
- async *readLines(path, options) {
335049
- const resolved = this._resolvePath(path);
335050
- const encoding = options?.encoding ?? "utf-8";
335051
- const errors = options?.errors ?? "strict";
335052
- if (!isUtf8Encoding(encoding)) {
335053
- yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readFile(resolved), encoding, errors));
335054
- return;
335055
- }
335056
- yield* this._readUtf8Lines(resolved, errors);
335057
- }
335058
- async scanTextFile(path) {
335059
- const fh = await open(this._resolvePath(path), "r");
335060
- try {
335061
- const buf = Buffer.alloc(READ_CHUNK_SIZE);
335062
- const flags = {
335063
- hasCrLf: false,
335064
- hasLf: false,
335065
- hasLoneCr: false
335066
- };
335067
- const validator = createUtf8Validator();
335068
- let totalLines = 0;
335069
- let totalBytes = 0;
335070
- let endsWithNewline = false;
335071
- let hasNul = false;
335072
- let prevWasCr = false;
335073
- while (true) {
335074
- const { bytesRead } = await fh.read(buf, 0, buf.length, null);
335075
- if (bytesRead === 0) break;
335076
- const chunk = buf.subarray(0, bytesRead);
335077
- validator.write(chunk);
335078
- for (let i = 0; i < chunk.length; i += 1) {
335079
- const byte = chunk[i];
335080
- if (byte === void 0) continue;
335081
- if (byte === 0) hasNul = true;
335082
- if (byte === 10) totalLines += 1;
335083
- }
335084
- prevWasCr = updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr);
335085
- totalBytes += bytesRead;
335086
- endsWithNewline = chunk[bytesRead - 1] === 10;
335087
- }
335088
- if (prevWasCr) flags.hasLoneCr = true;
335089
- validator.end();
335090
- if (totalBytes > 0 && !endsWithNewline) totalLines += 1;
335091
- return {
335092
- totalLines,
335093
- endsWithNewline,
335094
- hasNul,
335095
- lineEndingFlags: flags
335096
- };
335097
- } finally {
335098
- await fh.close();
335099
- }
335100
- }
335101
- async *readLineRange(path, options) {
335102
- const resolved = this._resolvePath(path);
335103
- const errors = options.errors ?? "strict";
335104
- yield* this._readUtf8Lines(resolved, errors, {
335105
- startLine: options.startLine,
335106
- maxLines: options.maxLines
335107
- });
335108
- }
335109
- async *readTailLines(path, options) {
335110
- if (options.tailCount <= 0) return;
335111
- const resolved = this._resolvePath(path);
335112
- const errors = options.errors ?? "strict";
335113
- const fh = await open(resolved, "r");
335114
- try {
335115
- const s = await fh.stat();
335116
- if (s.size === 0) return;
335117
- let pos = s.size;
335118
- let foundLf = 0;
335119
- let startOffset = 0;
335120
- let needLf = options.tailCount;
335121
- let sawTailBlock = false;
335122
- while (pos > 0 && foundLf < needLf) {
335123
- const readSize = Math.min(READ_CHUNK_SIZE, pos);
335124
- pos -= readSize;
335125
- const buf = Buffer.alloc(readSize);
335126
- await fh.read(buf, 0, readSize, pos);
335127
- if (!sawTailBlock) {
335128
- sawTailBlock = true;
335129
- needLf = buf[readSize - 1] === 10 ? options.tailCount + 1 : options.tailCount;
335130
- }
335131
- for (let i = readSize - 1; i >= 0; i -= 1) {
335132
- if (buf[i] !== 10) continue;
335133
- foundLf += 1;
335134
- if (foundLf === needLf) {
335135
- startOffset = pos + i + 1;
335136
- break;
335137
- }
335138
- }
335139
- }
335140
- if (foundLf < needLf) startOffset = 0;
335141
- yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readRange(fh, startOffset, s.size - startOffset), "utf-8", errors, startOffset !== 0));
335142
- } finally {
335143
- await fh.close();
335144
- }
335145
- }
335146
- async *_readUtf8Lines(resolved, errors, range) {
335147
- const startLine = range?.startLine ?? 1;
335148
- const maxLines = range?.maxLines ?? Number.POSITIVE_INFINITY;
335149
- const fh = await open(resolved, "r");
335150
- try {
335151
- const buf = Buffer.alloc(READ_CHUNK_SIZE);
335152
- let pending = [];
335153
- let pendingOffset = 0;
335154
- let fileOffset = 0;
335155
- let lineNo = 1;
335156
- let yielded = 0;
335157
- while (true) {
335158
- const { bytesRead } = await fh.read(buf, 0, buf.length, null);
335159
- if (bytesRead === 0) break;
335160
- const chunk = buf.subarray(0, bytesRead);
335161
- let lineStart = 0;
335162
- for (let i = 0; i < chunk.length; i += 1) {
335163
- if (chunk[i] !== 10) continue;
335164
- const piece = chunk.subarray(lineStart, i + 1);
335165
- const lineOffset = pending.length === 0 ? fileOffset + lineStart : pendingOffset;
335166
- const line = pending.length === 0 ? piece : Buffer.concat([...pending, piece]);
335167
- if (lineNo >= startLine) {
335168
- yield decodeTextWithErrors(line, "utf-8", errors, lineOffset !== 0);
335169
- yielded += 1;
335170
- if (yielded >= maxLines) return;
335171
- }
335172
- pending = [];
335173
- lineStart = i + 1;
335174
- lineNo += 1;
335175
- }
335176
- if (lineStart < chunk.length) {
335177
- const tail = Buffer.from(chunk.subarray(lineStart));
335178
- if (pending.length === 0) pendingOffset = fileOffset + lineStart;
335179
- pending.push(tail);
335180
- }
335181
- fileOffset += bytesRead;
335182
- }
335183
- if (pending.length > 0) {
335184
- const line = Buffer.concat(pending);
335185
- if (lineNo >= startLine) yield decodeTextWithErrors(line, "utf-8", errors, pendingOffset !== 0);
335186
- }
335187
- } finally {
335188
- await fh.close();
335189
- }
335190
- }
335191
- async writeBytes(path, data) {
335192
- await writeFile(this._resolvePath(path), data);
335193
- return data.length;
335194
- }
335195
- async writeText(path, data, options) {
335196
- const resolved = this._resolvePath(path);
335197
- const encoding = options?.encoding ?? "utf-8";
335198
- if ((options?.mode ?? "w") === "a") await appendFile(resolved, data, encoding);
335199
- else await writeFile(resolved, data, encoding);
335200
- return data.length;
335201
- }
335202
- async mkdir(path, options) {
335203
- const resolved = this._resolvePath(path);
335204
- const parents = options?.parents ?? false;
335205
- const existOk = options?.existOk ?? false;
335206
- if (parents) {
335207
- if (!existOk) try {
335208
- if ((await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists`);
335209
- } catch (error) {
335210
- if (error instanceof KaosFileExistsError) throw error;
335211
- if (error.code !== "ENOENT") throw error;
335212
- }
335213
- await mkdir(resolved, { recursive: true });
335214
- return;
335215
- }
335216
- try {
335217
- await mkdir(resolved);
335218
- } catch (error) {
335219
- if (existOk && error instanceof Error && "code" in error && error.code === "EEXIST") {
335220
- if (!(await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists but is not a directory`);
335221
- return;
335222
- }
335223
- throw error;
335224
- }
335225
- }
335226
- async exec(...args) {
335227
- const command = args[0];
335228
- if (command === void 0) throw new Error("LocalKaos.exec(): at least one argument (the command to run) is required.");
335229
- const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv()));
335230
- await waitForSpawn(child);
335231
- return new LocalProcess(child);
335232
- }
335233
- async execWithEnv(args, env) {
335234
- const command = args[0];
335235
- if (command === void 0) throw new Error("LocalKaos.execWithEnv(): at least one argument (the command to run) is required.");
335236
- const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv(env)));
335237
- await waitForSpawn(child);
335238
- return new LocalProcess(child);
335239
- }
335240
- _buildExecEnv(invocationEnv) {
335241
- if (this._envLayers.length === 0) return invocationEnv;
335242
- const merged = {
335243
- ...process.env,
335244
- ...invocationEnv
335245
- };
335246
- for (const layer of this._envLayers) Object.assign(merged, layer);
335247
- return merged;
335248
- }
335249
- };
335250
- }));
335251
- var init_current = __esmMin((() => {
335252
- new AsyncLocalStorage();
335253
- }));
335254
- //#endregion
335255
- //#region ../../packages/kaos/src/index.ts
335256
- var init_src$1 = __esmMin((() => {
335257
- init_environment$1();
335258
- init_errors$1();
335259
- init_local();
335260
- init_current();
335261
- }));
335262
- //#endregion
335263
335643
  //#region ../../packages/agent-core/src/agent/replay/build.ts
335264
335644
  async function buildReplay(persistence, range) {
335265
335645
  const agent = new Agent$1({
@@ -393762,7 +394142,7 @@ var require_constants$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
393762
394142
  };
393763
394143
  }));
393764
394144
  //#endregion
393765
- //#region ../../../../node_modules/node-gyp-build/node-gyp-build.js
394145
+ //#region ../../node_modules/node-gyp-build/node-gyp-build.js
393766
394146
  var require_node_gyp_build$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
393767
394147
  var fs$4 = __require("fs");
393768
394148
  var path$6 = __require("path");
@@ -393918,14 +394298,14 @@ var require_node_gyp_build$1 = /* @__PURE__ */ __commonJSMin(((exports, module)
393918
394298
  load.compareTuples = compareTuples;
393919
394299
  }));
393920
394300
  //#endregion
393921
- //#region ../../../../node_modules/node-gyp-build/index.js
394301
+ //#region ../../node_modules/node-gyp-build/index.js
393922
394302
  var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) => {
393923
394303
  const runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
393924
394304
  if (typeof runtimeRequire.addon === "function") module.exports = runtimeRequire.addon.bind(runtimeRequire);
393925
394305
  else module.exports = require_node_gyp_build$1();
393926
394306
  }));
393927
394307
  //#endregion
393928
- //#region ../../../../node_modules/bufferutil/fallback.js
394308
+ //#region ../../node_modules/bufferutil/fallback.js
393929
394309
  var require_fallback$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
393930
394310
  /**
393931
394311
  * Masks a buffer using the given mask.
@@ -393957,7 +394337,7 @@ var require_fallback$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
393957
394337
  };
393958
394338
  }));
393959
394339
  //#endregion
393960
- //#region ../../../../node_modules/bufferutil/index.js
394340
+ //#region ../../node_modules/bufferutil/index.js
393961
394341
  var require_bufferutil = /* @__PURE__ */ __commonJSMin(((exports, module) => {
393962
394342
  try {
393963
394343
  module.exports = require_node_gyp_build()(__dirname);
@@ -394450,7 +394830,7 @@ var require_permessage_deflate = /* @__PURE__ */ __commonJSMin(((exports, module
394450
394830
  }
394451
394831
  }));
394452
394832
  //#endregion
394453
- //#region ../../../../node_modules/utf-8-validate/fallback.js
394833
+ //#region ../../node_modules/utf-8-validate/fallback.js
394454
394834
  var require_fallback = /* @__PURE__ */ __commonJSMin(((exports, module) => {
394455
394835
  /**
394456
394836
  * Checks if a given buffer contains only correct UTF-8.
@@ -394480,7 +394860,7 @@ var require_fallback = /* @__PURE__ */ __commonJSMin(((exports, module) => {
394480
394860
  module.exports = isValidUTF8;
394481
394861
  }));
394482
394862
  //#endregion
394483
- //#region ../../../../node_modules/utf-8-validate/index.js
394863
+ //#region ../../node_modules/utf-8-validate/index.js
394484
394864
  var require_utf_8_validate = /* @__PURE__ */ __commonJSMin(((exports, module) => {
394485
394865
  try {
394486
394866
  module.exports = require_node_gyp_build()(__dirname);
@@ -531781,6 +532161,7 @@ var SessionEventHandler = class {
531781
532161
  }
531782
532162
  }
531783
532163
  handleSessionError(event) {
532164
+ if (event.code === "turn.agent_busy" && this.host.isManualPromptAdmissionPending?.(event.sessionId, event.agentId)) return;
531784
532165
  this.host.streamingUI.flushNow();
531785
532166
  this.host.streamingUI.resetToolUi();
531786
532167
  this.host.streamingUI.finalizeLiveTextBuffers("idle");
@@ -539090,17 +539471,22 @@ var BlunTUI = class {
539090
539471
  editorReplacement;
539091
539472
  editorReplacementGeneration = 0;
539092
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
+ }
539093
539479
  hasWaitingChannelMessage() {
539094
539480
  return this.state.queuedMessages.some((item) => item.mode === "channel");
539095
539481
  }
539096
539482
  canInterruptForQueuedChannel() {
539097
539483
  const item = this.state.queuedMessages[0];
539098
539484
  if (/^[1-9]\d*$/u.test(this.pendingChannelReplyGuard?.chatId ?? "") && item?.channelChatId?.startsWith("-") === true) return false;
539099
- 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();
539100
539486
  }
539101
539487
  canDeliverQueuedChannelHead() {
539102
539488
  const item = this.state.queuedMessages[0];
539103
- 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;
539104
539490
  const guard = this.pendingChannelReplyGuard;
539105
539491
  return guard === void 0 || item.channelChatId !== void 0 && guard.chatId === item.channelChatId;
539106
539492
  }
@@ -539224,7 +539610,7 @@ var BlunTUI = class {
539224
539610
  canDrainQueue() {
539225
539611
  const head = this.state.queuedMessages[0];
539226
539612
  const remoteControl = head?.mode === "command" && head.channelCommandEnvelope !== void 0 && channelCommandCanInterrupt(head.text);
539227
- 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);
539228
539614
  }
539229
539615
  scheduleQueueDrain() {
539230
539616
  if (this.queueDrainTimer !== void 0 || !this.canDrainQueue()) return;
@@ -539252,11 +539638,12 @@ var BlunTUI = class {
539252
539638
  }
539253
539639
  const inFlightCount = this.queueSteerInFlight?.items.length ?? 0;
539254
539640
  this.queueFlushBatchRemaining = inFlightCount + this.state.queuedMessages.length;
539641
+ if (this.manualPromptInFlight !== void 0) this.manualPromptInFlight.flushRequested = this.queueFlushBatchRemaining > 0;
539255
539642
  this.steerQueueFlushPrefix();
539256
539643
  this.scheduleQueueDrain();
539257
539644
  }
539258
539645
  steerQueueFlushPrefix() {
539259
- 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;
539260
539647
  const session = this.session;
539261
539648
  if (session === void 0 || this.state.appState.model.trim().length === 0) {
539262
539649
  this.showError(llmNotSetMessage());
@@ -539280,13 +539667,16 @@ var BlunTUI = class {
539280
539667
  turnEnded: false,
539281
539668
  onCommit: () => {
539282
539669
  this.queueFlushBatchRemaining = Math.max(0, this.queueFlushBatchRemaining - items.length);
539283
- for (const item of items) this.appendTranscriptEntry({
539284
- id: nextTranscriptId(),
539285
- kind: "user",
539286
- turnId,
539287
- renderMode: "plain",
539288
- content: item.text.trim()
539289
- });
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
+ }
539290
539680
  }
539291
539681
  };
539292
539682
  this.queueSteerInFlight = inFlight;
@@ -539358,7 +539748,7 @@ var BlunTUI = class {
539358
539748
  channelCommandEnvelope: envelope,
539359
539749
  channelAcknowledge: acknowledge
539360
539750
  };
539361
- 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;
539362
539752
  if (this.session === void 0 || this.deferUserMessages || this.queueCommandRunning || this.editorReplacementActive || busy && !channelCommandCanInterrupt(command)) {
539363
539753
  this.state.queuedMessages.push(item);
539364
539754
  this.scheduleQueueDrain();
@@ -539369,7 +539759,7 @@ var BlunTUI = class {
539369
539759
  }
539370
539760
  injectChannelEnvelope({
539371
539761
  canDeliver: () => this.session !== void 0 && this.state.appState.model.trim().length > 0,
539372
- 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,
539373
539763
  deliverNow: (modelInput, displayText, origin, contextOnly) => {
539374
539764
  this.sendChannelMessageInternal(this.requireSession(), modelInput, displayText, origin, envelope.meta.chat_id, envelope.meta["image_path"], contextOnly, false, acknowledge);
539375
539765
  },
@@ -539673,7 +540063,8 @@ var BlunTUI = class {
539673
540063
  agentId: this.harness.interactiveAgentId,
539674
540064
  parts: options?.parts,
539675
540065
  imageAttachmentIds: options?.imageAttachmentIds !== void 0 && options.imageAttachmentIds.length > 0 ? options.imageAttachmentIds : void 0,
539676
- mode
540066
+ mode,
540067
+ transcriptRendered: options?.transcriptRendered
539677
540068
  });
539678
540069
  this.track("input_queue");
539679
540070
  }
@@ -539732,7 +540123,8 @@ var BlunTUI = class {
539732
540123
  this.harness.withInteractiveAgent(item.agentId ?? "main", () => {
539733
540124
  this.sendMessageInternal(activeSession, item.text, {
539734
540125
  parts: item.parts,
539735
- imageAttachmentIds: item.imageAttachmentIds
540126
+ imageAttachmentIds: item.imageAttachmentIds,
540127
+ transcriptRendered: item.transcriptRendered
539736
540128
  });
539737
540129
  });
539738
540130
  }
@@ -539751,9 +540143,13 @@ var BlunTUI = class {
539751
540143
  this.sessionEventHandler.requestQueuedGoalPromotion();
539752
540144
  }
539753
540145
  sendMessageInternal(session, input, options) {
540146
+ if (this.manualPromptInFlight !== void 0) {
540147
+ this.enqueueMessage(input, options);
540148
+ return;
540149
+ }
539754
540150
  armPersonalMemoryRememberIntent(session.id, input);
539755
540151
  const imageAttachmentIds = options?.imageAttachmentIds !== void 0 && options.imageAttachmentIds.length > 0 ? options.imageAttachmentIds : void 0;
539756
- this.appendTranscriptEntry({
540152
+ if (!options?.transcriptRendered) this.appendTranscriptEntry({
539757
540153
  id: nextTranscriptId(),
539758
540154
  kind: "user",
539759
540155
  turnId: void 0,
@@ -539761,13 +540157,48 @@ var BlunTUI = class {
539761
540157
  content: input,
539762
540158
  imageAttachmentIds
539763
540159
  });
540160
+ const agentId = this.harness.interactiveAgentId;
540161
+ const inFlight = {
540162
+ session,
540163
+ agentId
540164
+ };
540165
+ this.manualPromptInFlight = inFlight;
540166
+ let admissionResolved = false;
539764
540167
  this.beginSessionRequest();
539765
540168
  const sdkInput = options?.parts ?? input;
539766
540169
  this.setAppState({
539767
540170
  model: BLUN_KING_MODEL_ALIAS,
539768
540171
  modelFallbackAllowed: false
539769
540172
  });
539770
- 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;
539771
540202
  finishPersonalMemoryRememberIntentTurn(session.id);
539772
540203
  const message = formatErrorMessage$2(error);
539773
540204
  this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: message }));
@@ -539794,19 +540225,20 @@ var BlunTUI = class {
539794
540225
  });
539795
540226
  }
539796
540227
  sendMessage(session, input, options) {
539797
- 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) {
539798
540229
  this.enqueueMessage(input, options);
540230
+ this.scheduleQueueDrain();
539799
540231
  return;
539800
540232
  }
539801
540233
  this.sendMessageInternal(session, input, options);
539802
540234
  }
539803
540235
  steerMessage(session, input) {
539804
- if (this.deferUserMessages || this.state.appState.isCompacting) {
540236
+ if (this.deferUserMessages || this.state.appState.isCompacting || this.manualPromptInFlight !== void 0) {
539805
540237
  for (const part of input) this.enqueueMessage(part);
539806
540238
  return;
539807
540239
  }
539808
540240
  if (this.state.appState.streamingPhase === "idle") {
539809
- for (const part of input) this.sendMessageInternal(session, part);
540241
+ for (const part of input) this.sendMessage(session, part);
539810
540242
  return;
539811
540243
  }
539812
540244
  for (const part of input) this.appendTranscriptEntry({
@@ -539826,6 +540258,7 @@ var BlunTUI = class {
539826
540258
  this.state.startupState = "ready";
539827
540259
  }
539828
540260
  clearQueuedMessages() {
540261
+ this.manualPromptInFlight = void 0;
539829
540262
  const discarded = [...this.state.queuedMessages, ...this.queueSteerInFlight?.items ?? []];
539830
540263
  this.queueSteerInFlight?.onRestore?.();
539831
540264
  this.state.queuedMessages = [];
@@ -540141,6 +540574,7 @@ var BlunTUI = class {
540141
540574
  this.state.terminal.setTitle(label);
540142
540575
  }
540143
540576
  resetSessionRuntime() {
540577
+ this.manualPromptInFlight = void 0;
540144
540578
  this.streamingUI.setSessionTotalTokens(void 0);
540145
540579
  this.loopCountdown.dispose();
540146
540580
  this.aborted = false;