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/worker-host.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);
@@ -9,14 +9,14 @@ import crypto$1, { createHash, randomBytes, randomInt, randomUUID } from "node:c
9
9
  import * as fs$12 from "node:fs";
10
10
  import Kt, { appendFileSync, chmodSync, closeSync, constants, createReadStream, createWriteStream, existsSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, opendirSync, promises, readFileSync, readSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
11
11
  import * as path$11 from "node:path";
12
- import path, { basename, dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
12
+ import path, { basename, dirname, isAbsolute, join, parse, posix, relative, resolve, sep, win32 } from "node:path";
13
13
  import { Blob as Blob$1, File as File$1 } from "node:buffer";
14
14
  import * as nodeOs from "node:os";
15
15
  import os, { arch, homedir, hostname, release, tmpdir, type, userInfo } from "node:os";
16
16
  import fs, { access, appendFile, chmod, copyFile, cp, link, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
17
17
  import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
18
18
  import * as sysPath from "path";
19
- import path$1, { dirname as dirname$1, parse } from "path";
19
+ import path$1, { dirname as dirname$1, parse as parse$1 } from "path";
20
20
  import I, { existsSync as existsSync$1, promises as promises$1, stat as stat$1, unwatchFile, watch, watchFile } from "fs";
21
21
  import zi from "assert";
22
22
  import Qr, { EventEmitter } from "events";
@@ -469,7 +469,7 @@ const basename$1 = function(p, extension) {
469
469
  }
470
470
  return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
471
471
  };
472
- const parse$8 = function(p) {
472
+ const parse$9 = function(p) {
473
473
  const root = _PATH_ROOT_RE.exec(p)?.[0]?.replace(/\\/g, "/") || "";
474
474
  const base = basename$1(p);
475
475
  const extension = extname(base);
@@ -495,7 +495,7 @@ const _path = {
495
495
  matchesGlob,
496
496
  normalize: normalize$3,
497
497
  normalizeString,
498
- parse: parse$8,
498
+ parse: parse$9,
499
499
  relative: relative$1,
500
500
  resolve: resolve$2,
501
501
  sep: "/",
@@ -8984,7 +8984,7 @@ const ZodError$1 = $constructor("ZodError", initializer);
8984
8984
  const ZodRealError = $constructor("ZodError", initializer, { Parent: Error });
8985
8985
  //#endregion
8986
8986
  //#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/parse.js
8987
- const parse$7 = /* @__PURE__ */ _parse(ZodRealError);
8987
+ const parse$8 = /* @__PURE__ */ _parse(ZodRealError);
8988
8988
  const parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
8989
8989
  const safeParse$1 = /* @__PURE__ */ _safeParse(ZodRealError);
8990
8990
  const safeParseAsync = /* @__PURE__ */ _safeParseAsync(ZodRealError);
@@ -9022,7 +9022,7 @@ const ZodType$1 = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
9022
9022
  reg.add(inst, meta);
9023
9023
  return inst;
9024
9024
  });
9025
- inst.parse = (data, params) => parse$7(inst, data, params, { callee: inst.parse });
9025
+ inst.parse = (data, params) => parse$8(inst, data, params, { callee: inst.parse });
9026
9026
  inst.safeParse = (data, params) => safeParse$1(inst, data, params);
9027
9027
  inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });
9028
9028
  inst.safeParseAsync = async (data, params) => safeParseAsync(inst, data, params);
@@ -15233,7 +15233,7 @@ function peekTable(key, table, meta, type) {
15233
15233
  state.c
15234
15234
  ];
15235
15235
  }
15236
- function parse$6(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
15236
+ function parse$7(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
15237
15237
  let res = {};
15238
15238
  let meta = {};
15239
15239
  let tbl = res;
@@ -15486,7 +15486,7 @@ function loadRuntimeConfigSafe(filePath, env = process.env) {
15486
15486
  if (text !== void 0 && text.trim().length > 0) {
15487
15487
  let data;
15488
15488
  try {
15489
- data = parse$6(text);
15489
+ data = parse$7(text);
15490
15490
  } catch (error) {
15491
15491
  fileError = new BlunError(ErrorCodes.CONFIG_INVALID, `Invalid TOML in ${filePath}: ${describeUnknownError(error)}`, { cause: error });
15492
15492
  fileWarnings.push(`Invalid TOML in ${filePath}: ${describeTomlSyntaxError(error)}.`);
@@ -15568,7 +15568,7 @@ function parseConfigString(tomlText, filePath = "config.toml") {
15568
15568
  if (tomlText.trim().length === 0) return getDefaultConfig();
15569
15569
  let data;
15570
15570
  try {
15571
- data = parse$6(tomlText);
15571
+ data = parse$7(tomlText);
15572
15572
  } catch (error) {
15573
15573
  throw new BlunError(ErrorCodes.CONFIG_INVALID, `Invalid TOML in ${filePath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
15574
15574
  }
@@ -15967,7 +15967,7 @@ async function readWorkspaceLocalToml(kaos, configPath) {
15967
15967
  };
15968
15968
  let raw;
15969
15969
  try {
15970
- raw = parse$6(text);
15970
+ raw = parse$7(text);
15971
15971
  } catch (error) {
15972
15972
  throw new BlunError(ErrorCodes.CONFIG_INVALID, `Invalid TOML in ${configPath}: ${describeError$1(error)}`, { cause: error });
15973
15973
  }
@@ -180434,7 +180434,10 @@ function applyCompletionBudgetWithDetails(args) {
180434
180434
  });
180435
180435
  if (args.retry !== void 0) cap = Math.max(args.retry.minimumCompletionTokens, Math.ceil(cap * args.retry.multiplier));
180436
180436
  const maxContextTokens = args.capability?.max_context_tokens;
180437
- if (args.usedContextTokens !== void 0 && maxContextTokens !== void 0 && maxContextTokens > 0) cap = Math.max(MIN_FLOOR, Math.min(cap, maxContextTokens - args.usedContextTokens));
180437
+ if (args.usedContextTokens !== void 0 && maxContextTokens !== void 0 && maxContextTokens > 0) {
180438
+ const headroom = Math.min(2048, Math.ceil(maxContextTokens * .01));
180439
+ cap = Math.max(MIN_FLOOR, Math.min(cap, maxContextTokens - args.usedContextTokens - headroom));
180440
+ }
180438
180441
  return {
180439
180442
  provider: args.provider.withMaxCompletionTokens(cap, {
180440
180443
  usedContextTokens: args.usedContextTokens,
@@ -229582,7 +229585,7 @@ var FullCompaction = class {
229582
229585
  this.agent.log.error("failed to refresh system prompt after compaction", { error });
229583
229586
  }
229584
229587
  await this.agent.injection.injectAfterCompaction();
229585
- this.fitRetainedInput();
229588
+ this.fitRetainedInput({}, result.keptHeadUserMessageCount !== void 0);
229586
229589
  this.lastCompactedState = {
229587
229590
  tokenCount: this.tokenCountWithPending,
229588
229591
  modelAlias: this.agent.activeResponderModel ?? this.agent.config.modelAlias,
@@ -229618,17 +229621,18 @@ var FullCompaction = class {
229618
229621
  this.agent.turn.onCompactionFinished();
229619
229622
  }
229620
229623
  }
229621
- fitRetainedInput(context = {}) {
229624
+ fitRetainedInput(context = {}, freshBudgetedSelection = false) {
229622
229625
  const budget = this.getCompactionBudgetTokens();
229623
229626
  const window = this.getEffectiveMaxContextTokens();
229624
229627
  const limit = window > 0 ? window - 1 : Infinity;
229628
+ const hardContextLimit = window > 0 && !freshBudgetedSelection ? window : void 0;
229625
229629
  const estimate = (messages) => this.estimateProjectedRequestTokens({
229626
229630
  ...context,
229627
229631
  messages
229628
229632
  });
229629
229633
  const preserveLatestUser = this.agent.turn.hasActiveTurn;
229630
- if (budget === void 0) return this.agent.context.fitCompactionPrefix(limit, estimate, preserveLatestUser);
229631
- 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);
229634
+ if (budget === void 0) return this.agent.context.fitCompactionPrefix(limit, estimate, preserveLatestUser, hardContextLimit);
229635
+ 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);
229632
229636
  }
229633
229637
  buildInstruction(customInstruction) {
229634
229638
  return renderPrompt(compaction_instruction_default, { customInstruction: customInstruction?.trim() ?? "" }).trimEnd();
@@ -232498,7 +232502,7 @@ function expandUserPath(value, pathClass, homeDir) {
232498
232502
  }
232499
232503
  function defaultCwdForPath(value) {
232500
232504
  if (!isAbsolute$1(value)) return void 0;
232501
- return parse$8(value).root;
232505
+ return parse$9(value).root;
232502
232506
  }
232503
232507
  function pathMatchSemantics(value, pattern, pathOptions) {
232504
232508
  return { pathClass: pathOptions?.pathClass ?? ([value, pattern].some((candidate) => {
@@ -233588,7 +233592,7 @@ var ContextMemory = class {
233588
233592
  this.agent.microCompaction.reset();
233589
233593
  this.agent.emitStatusUpdated();
233590
233594
  }
233591
- fitCompactionPrefix(maxRequestTokens, estimateRequest, preserveLatestUser = false) {
233595
+ fitCompactionPrefix(maxRequestTokens, estimateRequest, preserveLatestUser = false, hardContextLimit) {
233592
233596
  const prefixLength = this._history.findIndex((message) => message.origin?.kind === "compaction_summary");
233593
233597
  if (prefixLength <= 0 || !Number.isSafeInteger(maxRequestTokens) || maxRequestTokens <= 0) return false;
233594
233598
  const suffix = this._history.slice(prefixLength);
@@ -233600,7 +233604,9 @@ var ContextMemory = class {
233600
233604
  ];
233601
233605
  return Math.max(estimateTokensForMessages(messages), estimateRequest(project(messages, { dropOrphanResults: true })));
233602
233606
  };
233603
- if (estimate(this._history.slice(0, prefixLength)) <= maxRequestTokens) return false;
233607
+ const requestTokens = estimate(this._history.slice(0, prefixLength));
233608
+ if (requestTokens <= maxRequestTokens) return false;
233609
+ if (hardContextLimit !== void 0 && requestTokens >= hardContextLimit) return false;
233604
233610
  const sourceKey = Symbol("compaction source");
233605
233611
  const sources = collectCompactableUserMessages(this._history.slice(0, prefixLength)).map((message) => ({
233606
233612
  ...message,
@@ -234490,6 +234496,9 @@ var HookEngine = class {
234490
234496
  for (const [event, hooks] of this.byEvent.entries()) result[event] = hooks.length;
234491
234497
  return result;
234492
234498
  }
234499
+ hasMatchingHook(event, matcherValue) {
234500
+ return this.matchingHooks(event, matcherValue).length > 0;
234501
+ }
234493
234502
  closeAdmission() {
234494
234503
  this.admissionClosed = true;
234495
234504
  }
@@ -258561,7 +258570,7 @@ var vn = (s) => {
258561
258570
  }, Qi = (s, t) => {
258562
258571
  let e = new Map(t.map((o) => [mt(o), !0])), i = s.filter, r = 100, n = (o, h = "", a = 0) => {
258563
258572
  if (a >= r) return e.set(o, !1), !1;
258564
- let l = h || parse(o).root || ".", c;
258573
+ let l = h || parse$1(o).root || ".", c;
258565
258574
  if (o === l) c = !1;
258566
258575
  else {
258567
258576
  let d = e.get(o);
@@ -264005,450 +264014,1899 @@ function safeToolResultFileStem(toolName, toolCallId) {
264005
264014
  return `${toolName}-${toolCallId}`.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 80) || "tool-result";
264006
264015
  }
264007
264016
  //#endregion
264008
- //#region ../../packages/agent-core/src/agent/response-review-decision.ts
264009
- const STAGES = [
264010
- "setup",
264011
- "native",
264012
- "release",
264013
- "verification"
264014
- ];
264015
- const REASONS = [
264016
- "invalid_settings",
264017
- "connection_unavailable",
264018
- "connection_changed",
264019
- "native_unavailable",
264020
- "native_rejected",
264021
- "release_denied",
264022
- "release_rejected",
264023
- "verification_denied",
264024
- "verification_rejected",
264025
- "binding_mismatch",
264026
- "cancelled",
264027
- "exception"
264028
- ];
264029
- const FINDINGS = new Set([
264030
- "empty-target",
264031
- "unicode-not-nfc",
264032
- "replacement-character",
264033
- "nul-character",
264034
- "script-mismatch",
264035
- "missing-language-character-profile",
264036
- "ascii-folding-pressure",
264037
- "short-text-native-review-required",
264038
- "suspected-ascii-substitution",
264039
- "invalid-target-type",
264040
- "exact-language-required",
264041
- "missing-response-attestations",
264042
- "isolated-guard-unavailable",
264043
- "unrecognized-finding"
264044
- ]);
264045
- /** Never carry provider/guard prose, signed receipts or arbitrary codes into logs. */
264046
- function rejectionFindings(report) {
264047
- const codes = [];
264048
- if (report?.["reason"] !== void 0) codes.push(report["reason"]);
264049
- if (Array.isArray(report?.["findings"])) for (const item of report["findings"].slice(0, 32)) codes.push(typeof item === "object" && item !== null && "code" in item ? item.code : void 0);
264050
- const safe = [...new Set(codes.map((code) => typeof code === "string" && FINDINGS.has(code) ? code : "unrecognized-finding"))];
264051
- return safe.length ? safe : void 0;
264017
+ //#region ../../packages/kaos/src/errors.ts
264018
+ /**
264019
+ * Base error class for the kaos package.
264020
+ */
264021
+ var KaosError = class extends Error {
264022
+ constructor(message) {
264023
+ super(message);
264024
+ this.name = "KaosError";
264025
+ }
264026
+ };
264027
+ /**
264028
+ * Equivalent to Python's FileExistsError — indicates a file or directory already exists.
264029
+ */
264030
+ var KaosFileExistsError = class extends KaosError {
264031
+ constructor(message) {
264032
+ super(message);
264033
+ this.name = "KaosFileExistsError";
264034
+ }
264035
+ };
264036
+ /**
264037
+ * Thrown by `detectEnvironment` on Windows when no Git Bash install can be
264038
+ * located. Carries the list of paths that were probed so callers can include
264039
+ * them in install hints.
264040
+ */
264041
+ var KaosShellNotFoundError = class extends KaosError {
264042
+ constructor(message) {
264043
+ super(message);
264044
+ this.name = "KaosShellNotFoundError";
264045
+ }
264046
+ };
264047
+ //#endregion
264048
+ //#region ../../packages/kaos/src/environment.ts
264049
+ /**
264050
+ * Environment — cross-platform probe of OS / shell.
264051
+ *
264052
+ * Detection is a pure function of injected probes (`platform` / `arch` /
264053
+ * `release` / `env` / `isFile` / `execFileText`) so the same suite runs
264054
+ * identically on any host OS. `detectEnvironmentFromNode()` bundles the Node
264055
+ * defaults for production callers.
264056
+ *
264057
+ * On Windows the probe expects Git Bash (the canonical POSIX shell that
264058
+ * ships with Git for Windows). If it cannot be located the function
264059
+ * throws `KaosShellNotFoundError`; the SDK layer can wrap that into a
264060
+ * user-facing install hint. Set `BLUN_SHELL_PATH` to override.
264061
+ */
264062
+ const GIT_EXEC_PATH_TIMEOUT_MS = 5e3;
264063
+ function resolveOsKind(platform) {
264064
+ switch (platform) {
264065
+ case "darwin": return "macOS";
264066
+ case "linux": return "Linux";
264067
+ case "win32": return "Windows";
264068
+ default: return platform;
264069
+ }
264052
264070
  }
264053
- function rejectionMessage(value) {
264054
- if (!STAGES.includes(value.stage) || !REASONS.includes(value.reason)) return void 0;
264055
- const findings = rejectionFindings({ findings: value.findings?.map((code) => ({ code })) });
264056
- return `Response review rejected candidate: ${value.stage}/${value.reason}` + (findings?.length ? ` (${findings.join(", ")})` : "");
264071
+ async function detectEnvironment(deps) {
264072
+ const osKind = resolveOsKind(deps.platform);
264073
+ const osArch = deps.arch;
264074
+ const osVersion = deps.release;
264075
+ if (deps.platform === "win32") return {
264076
+ osKind,
264077
+ osArch,
264078
+ osVersion,
264079
+ shellName: "bash",
264080
+ shellPath: await locateWindowsGitBash(deps)
264081
+ };
264082
+ const candidates = [
264083
+ "/bin/bash",
264084
+ "/usr/bin/bash",
264085
+ "/usr/local/bin/bash"
264086
+ ];
264087
+ let found;
264088
+ for (const p of candidates) if (await deps.isFile(p)) {
264089
+ found = p;
264090
+ break;
264091
+ }
264092
+ if (found !== void 0) return {
264093
+ osKind,
264094
+ osArch,
264095
+ osVersion,
264096
+ shellName: "bash",
264097
+ shellPath: found
264098
+ };
264099
+ return {
264100
+ osKind,
264101
+ osArch,
264102
+ osVersion,
264103
+ shellName: "sh",
264104
+ shellPath: "/bin/sh"
264105
+ };
264057
264106
  }
264058
- //#endregion
264059
- //#region ../../packages/agent-core/src/agent/response-review.ts
264060
- const MAX_CANDIDATE_BYTES = 1048576;
264061
- const MAX_CANDIDATE_PARTS = 4096;
264062
- const REVIEW_TIMEOUT_MS = 3e4;
264063
- const REVIEW_CANCEL_SETTLE_MS = 1e3;
264064
- const MAX_REVIEW_ATTEMPTS = 2;
264065
- function createReviewedOutputDispatcher(options) {
264066
- let pending = [];
264067
- let bytes = 0;
264068
- let stepUuid;
264069
- let epoch = 0;
264070
- let reviewing = false;
264071
- let turnId;
264072
- let stepNumber = 0;
264073
- let toolTextWithheld = false;
264074
- const toolCalls = /* @__PURE__ */ new Map();
264075
- const toolResults = /* @__PURE__ */ new Set();
264076
- let invalidToolExchange = false;
264077
- function clear() {
264078
- pending = [];
264079
- bytes = 0;
264080
- stepUuid = void 0;
264081
- epoch += 1;
264082
- reviewing = false;
264083
- toolCalls.clear();
264084
- toolResults.clear();
264085
- invalidToolExchange = false;
264107
+ async function locateWindowsGitBash(deps) {
264108
+ const checked = [];
264109
+ const override = deps.env["BLUN_SHELL_PATH"]?.trim();
264110
+ if (override !== void 0 && override.length > 0) {
264111
+ checked.push(override);
264112
+ if (await deps.isFile(override)) return override;
264086
264113
  }
264087
- function dispatch(event) {
264088
- if (event.type === "text.delta") {
264089
- options.onTextProgress?.();
264090
- return;
264091
- }
264092
- if (event.type === "step.begin") {
264093
- clear();
264094
- if (turnId !== event.turnId) toolTextWithheld = false;
264095
- turnId = event.turnId;
264096
- stepNumber = event.step;
264097
- stepUuid = event.uuid;
264098
- return options.dispatch(event);
264099
- }
264100
- if (event.type === "content.part" && event.part.type === "text") {
264101
- if (reviewing) {
264102
- clear();
264103
- return Promise.reject(/* @__PURE__ */ new Error("Response review candidate changed during verification"));
264104
- }
264105
- if (stepUuid !== event.stepUuid) return Promise.reject(/* @__PURE__ */ new Error("Response review step mismatch"));
264106
- bytes += Buffer.byteLength(event.part.text);
264107
- if (bytes > MAX_CANDIDATE_BYTES || pending.length >= MAX_CANDIDATE_PARTS) {
264108
- clear();
264109
- return Promise.reject(/* @__PURE__ */ new Error("Response review candidate exceeds limit"));
264110
- }
264111
- pending.push({
264112
- ...event,
264113
- part: { ...event.part }
264114
- });
264115
- return Promise.resolve();
264116
- }
264117
- if (event.type === "step.end") return finish(event);
264118
- if (event.type === "turn.interrupted") clear();
264119
- if (event.type === "step.retrying") {
264120
- clear();
264121
- stepUuid = event.stepUuid;
264122
- }
264123
- if (event.type === "tool.call") {
264124
- const recordedEpoch = epoch;
264125
- const { toolCallId, uuid } = event;
264126
- const matchesStep = event.stepUuid === stepUuid && event.turnId === turnId && event.step === stepNumber;
264127
- return options.dispatch(event).then(() => {
264128
- if (recordedEpoch !== epoch) return;
264129
- if (!matchesStep || toolCalls.has(toolCallId)) invalidToolExchange = true;
264130
- else toolCalls.set(toolCallId, uuid);
264131
- });
264114
+ const gitExecutables = await findExecutablesOnPath("git.exe", deps.env["PATH"], deps.platform, deps.isFile);
264115
+ for (const gitExe of gitExecutables) {
264116
+ const inferred = gitBashCandidatesFromGitExe(gitExe);
264117
+ if (inferred !== void 0) for (const candidate of inferred) {
264118
+ checked.push(candidate);
264119
+ if (await deps.isFile(candidate)) return candidate;
264132
264120
  }
264133
- if (event.type === "tool.result") {
264134
- const recordedEpoch = epoch;
264135
- const { toolCallId, parentUuid } = event;
264136
- return options.dispatch(event).then(() => {
264137
- if (recordedEpoch !== epoch) return;
264138
- if (toolCalls.get(toolCallId) !== parentUuid || toolResults.has(toolCallId)) invalidToolExchange = true;
264139
- else toolResults.add(toolCallId);
264140
- });
264121
+ const gitExecPath = await readGitExecPath(deps, gitExe);
264122
+ if (gitExecPath === void 0) continue;
264123
+ for (const candidate of gitBashCandidatesFromGitExecPath(gitExecPath)) {
264124
+ checked.push(candidate);
264125
+ if (await deps.isFile(candidate)) return candidate;
264141
264126
  }
264142
- if (event.type === "content.part") return options.dispatch(event);
264143
- options.dispatch(event);
264144
264127
  }
264145
- async function finish(event) {
264146
- if (pending.length === 0) return options.dispatch(event);
264147
- if (event.uuid !== stepUuid) throw new Error("Response review step mismatch");
264148
- const currentEpoch = epoch;
264149
- const parts = [...pending];
264150
- reviewing = true;
264151
- const text = parts.map((part) => part.part.type === "text" ? part.part.text : "").join("");
264152
- try {
264153
- if (event.finishReason !== "end_turn" && event.finishReason !== "tool_use") throw new Error("Response review requires a complete candidate");
264154
- if (options.review === void 0) throw new Error("Response review unavailable");
264155
- options.signal.throwIfAborted();
264156
- let approval;
264157
- let rejection;
264158
- let nativeRejection = false;
264159
- for (let attempt = 1; attempt <= MAX_REVIEW_ATTEMPTS; attempt += 1) {
264160
- options.signal.throwIfAborted();
264161
- if (epoch !== currentEpoch) throw new Error("Response review candidate changed during verification");
264162
- const deadline = createDeadlineAbortSignal(options.signal, REVIEW_TIMEOUT_MS);
264163
- let requested;
264164
- let attemptRejection;
264165
- let attemptNativeRejection = false;
264166
- try {
264167
- requested = options.review({
264168
- text,
264169
- turnId: event.turnId,
264170
- step: event.step,
264171
- signal: deadline.signal,
264172
- onRejected: (decision) => {
264173
- if (!deadline.signal.aborted) {
264174
- const message = rejectionMessage(decision);
264175
- attemptNativeRejection = attemptRejection === void 0 && message !== void 0 && decision.stage === "native" && decision.reason === "native_rejected";
264176
- attemptRejection = message;
264177
- }
264178
- }
264179
- });
264180
- approval = await abortable(requested, deadline.signal);
264181
- rejection = attemptRejection;
264182
- nativeRejection = attemptNativeRejection;
264183
- break;
264184
- } catch {
264185
- options.signal.throwIfAborted();
264186
- if (attemptRejection !== void 0) throw new Error(attemptRejection);
264187
- if (deadline.timedOut() && attempt < MAX_REVIEW_ATTEMPTS && requested !== void 0 && epoch === currentEpoch && await waitForCancelledReview(requested)) continue;
264188
- throw new Error(deadline.timedOut() ? "Response review timed out" : "Response review failed");
264189
- } finally {
264190
- deadline.clear();
264191
- }
264192
- }
264193
- options.signal.throwIfAborted();
264194
- if (epoch !== currentEpoch) throw new Error("Response review candidate changed during verification");
264195
- if (nativeRejection && !toolTextWithheld && event.finishReason === "tool_use" && event.turnId === turnId && event.step === stepNumber && !invalidToolExchange && toolCalls.size > 0 && toolResults.size === toolCalls.size) {
264196
- toolTextWithheld = true;
264197
- await options.dispatch(event);
264198
- try {
264199
- options.onToolTextWithheld?.({
264200
- turnId: event.turnId,
264201
- step: event.step
264202
- });
264203
- } catch {}
264204
- return;
264205
- }
264206
- if (rejection !== void 0) throw new Error(rejection);
264207
- if (epoch !== currentEpoch || approval?.targetSha256 !== createHash("sha256").update(text).digest("hex") || !Number.isSafeInteger(approval.expiresAt) || approval.expiresAt <= Date.now()) throw new Error("Response review did not approve the exact current candidate");
264208
- for (const part of parts) {
264209
- options.signal.throwIfAborted();
264210
- if (epoch !== currentEpoch || approval.expiresAt <= Date.now()) throw new Error("Response review approval no longer current");
264211
- await options.dispatch(part);
264212
- }
264213
- options.signal.throwIfAborted();
264214
- if (epoch !== currentEpoch || approval.expiresAt <= Date.now()) throw new Error("Response review approval no longer current");
264215
- options.dispatch({
264216
- type: "text.delta",
264217
- delta: text
264218
- });
264219
- await options.dispatch(event);
264220
- } finally {
264221
- if (epoch === currentEpoch) clear();
264128
+ const candidates = [
264129
+ "C:\\Program Files\\Git\\bin\\bash.exe",
264130
+ "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
264131
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
264132
+ "C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
264133
+ ];
264134
+ const localAppData = deps.env["LOCALAPPDATA"]?.trim();
264135
+ if (localAppData !== void 0 && localAppData.length > 0) {
264136
+ candidates.push(`${localAppData}\\Programs\\Git\\bin\\bash.exe`);
264137
+ candidates.push(`${localAppData}\\Programs\\Git\\usr\\bin\\bash.exe`);
264138
+ }
264139
+ for (const candidate of candidates) {
264140
+ checked.push(candidate);
264141
+ if (await deps.isFile(candidate)) return candidate;
264142
+ }
264143
+ 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(", ")}.`);
264144
+ }
264145
+ async function readGitExecPath(deps, gitExe) {
264146
+ if (deps.platform === "win32" && !isAbsoluteWindowsPath(gitExe)) return void 0;
264147
+ const stdout = await deps.execFileText(gitExe, ["--exec-path"], GIT_EXEC_PATH_TIMEOUT_MS);
264148
+ if (stdout === void 0) return void 0;
264149
+ for (const line of stdout.split(/\r?\n/)) {
264150
+ const execPath = line.trim();
264151
+ if (execPath.length > 0) return execPath;
264152
+ }
264153
+ }
264154
+ function gitBashCandidatesFromGitExe(gitExe) {
264155
+ const normalizedGitExe = path$11.win32.normalize(normalizeWindowsPath(gitExe));
264156
+ const gitDir = path$11.win32.dirname(normalizedGitExe);
264157
+ const gitDirName = path$11.win32.basename(gitDir).toLowerCase();
264158
+ if (gitDirName !== "cmd" && gitDirName !== "bin") return;
264159
+ return gitBashCandidatesFromGitRoot(path$11.win32.dirname(gitDir));
264160
+ }
264161
+ function gitBashCandidatesFromGitExecPath(execPath) {
264162
+ const normalized = path$11.win32.normalize(normalizeWindowsPath(execPath));
264163
+ const parts = normalized.split("\\");
264164
+ for (let i = parts.length - 1; i >= 0; i -= 1) {
264165
+ const segment = parts[i]?.toLowerCase();
264166
+ if (segment === "mingw32" || segment === "mingw64") {
264167
+ const root = parts.slice(0, i).join("\\");
264168
+ if (root.length > 0) return gitBashCandidatesFromGitRoot(root);
264222
264169
  }
264223
264170
  }
264224
- async function waitForCancelledReview(requested) {
264225
- const deadline = createDeadlineAbortSignal(options.signal, REVIEW_CANCEL_SETTLE_MS);
264171
+ return gitBashCandidatesFromGitRoot(path$11.win32.join(normalized, "..", ".."));
264172
+ }
264173
+ function gitBashCandidatesFromGitRoot(root) {
264174
+ return [path$11.win32.normalize(path$11.win32.join(root, "bin", "bash.exe")), path$11.win32.normalize(path$11.win32.join(root, "usr", "bin", "bash.exe"))];
264175
+ }
264176
+ function normalizeWindowsPath(path) {
264177
+ return path.replaceAll("/", "\\");
264178
+ }
264179
+ function isAbsoluteWindowsPath(path) {
264180
+ return path$11.win32.isAbsolute(normalizeWindowsPath(path));
264181
+ }
264182
+ function dedupeWindowsPaths(paths) {
264183
+ const deduped = [];
264184
+ const seen = /* @__PURE__ */ new Set();
264185
+ for (const path of paths) {
264186
+ const key = normalizeWindowsPath(path).toLowerCase();
264187
+ if (seen.has(key)) continue;
264188
+ seen.add(key);
264189
+ deduped.push(path);
264190
+ }
264191
+ return deduped;
264192
+ }
264193
+ /**
264194
+ * Production convenience — derive the deps bag from Node's ambient surface.
264195
+ *
264196
+ * The result is memoised: subsequent calls return the original promise.
264197
+ * `Environment` is immutable for the lifetime of the process (it derives
264198
+ * from `process.platform`, `process.arch`, `os.release()`, and one-time
264199
+ * shell-path discovery), so caching is sound. Tests that need to probe
264200
+ * with different inputs should call {@link detectEnvironment} directly
264201
+ * with an injected deps bag.
264202
+ */
264203
+ let detectedEnvironment;
264204
+ function detectEnvironmentFromNode() {
264205
+ if (detectedEnvironment !== void 0) return detectedEnvironment;
264206
+ const platform = process.platform;
264207
+ const env = process.env;
264208
+ const isFile = async (path) => {
264226
264209
  try {
264227
- await abortable(requested.then(() => void 0, () => void 0), deadline.signal);
264228
- options.signal.throwIfAborted();
264210
+ await access(path, constants.F_OK);
264229
264211
  return true;
264230
264212
  } catch {
264231
- options.signal.throwIfAborted();
264232
264213
  return false;
264233
- } finally {
264234
- deadline.clear();
264235
264214
  }
264215
+ };
264216
+ detectedEnvironment = detectEnvironment({
264217
+ platform,
264218
+ arch: process.arch,
264219
+ release: nodeOs.release(),
264220
+ env,
264221
+ isFile,
264222
+ execFileText
264223
+ });
264224
+ return detectedEnvironment;
264225
+ }
264226
+ async function findExecutablesOnPath(name, pathEnv, platform, isFile) {
264227
+ if (pathEnv === void 0 || pathEnv.length === 0) return [];
264228
+ const listSep = platform === "win32" ? ";" : ":";
264229
+ const dirSep = platform === "win32" ? "\\" : "/";
264230
+ const paths = [];
264231
+ for (const rawDir of pathEnv.split(listSep)) {
264232
+ const dir = rawDir.trim();
264233
+ if (dir.length === 0) continue;
264234
+ if (platform === "win32" && !isAbsoluteWindowsPath(dir)) continue;
264235
+ const candidate = dir.endsWith(dirSep) ? `${dir}${name}` : `${dir}${dirSep}${name}`;
264236
+ if (await isFile(candidate)) paths.push(candidate);
264236
264237
  }
264237
- return dispatch;
264238
+ return platform === "win32" ? dedupeWindowsPaths(paths) : paths;
264239
+ }
264240
+ async function execFileText(file, args, timeoutMs) {
264241
+ return new Promise((resolve) => {
264242
+ execFile(file, [...args], {
264243
+ encoding: "utf8",
264244
+ timeout: timeoutMs,
264245
+ windowsHide: true
264246
+ }, (error, stdout) => {
264247
+ if (error !== null) {
264248
+ resolve(void 0);
264249
+ return;
264250
+ }
264251
+ resolve(stdout);
264252
+ });
264253
+ });
264238
264254
  }
264239
264255
  //#endregion
264240
- //#region ../../packages/agent-core/src/agent/turn/index.ts
264241
- function blunExtractText(content) {
264242
- if (typeof content === "string") return content;
264243
- if (Array.isArray(content)) return content.map((c) => typeof c === "string" ? c : c && typeof c === "object" && "text" in c && typeof c.text === "string" ? c.text : "").join(" ");
264244
- return "";
264256
+ //#region ../../packages/kaos/src/internal.ts
264257
+ function isUtf8Continuation(byte) {
264258
+ return byte >= 128 && byte <= 191;
264245
264259
  }
264246
- /**
264247
- * BLUN: decide whether a turn should carry the full tool set. Small-context models
264248
- * (e.g. King on an 8k window) lose their identity/language when all tool schemas
264249
- * flood the prompt. A plain greeting omits task tools but keeps any enabled
264250
- * language review workflow. Conservative: only very short, clearly
264251
- * conversational messages skip task tools; anything task-like keeps the full set.
264252
- */
264253
- function blunTurnNeedsTools(input, origin) {
264254
- if (origin.kind !== "user") return true;
264255
- const text = blunExtractText(input).trim().toLowerCase();
264256
- if (text.length === 0 || text.length > 48) return true;
264257
- return !/^(?:hi|hallo|hey|hello|hoi|moin|servus|yo|danke|thanks|thx|bye|tschau|ciao)[.!?]*$/.test(text) && !/^(?:wer bist du|who are you|wie geht(?: es dir)?|how are you|na)[.!?]*$/.test(text);
264260
+ function decodeUtf8Ignore(data) {
264261
+ let output = "";
264262
+ let i = 0;
264263
+ while (i < data.length) {
264264
+ const b0 = data[i];
264265
+ if (b0 === void 0) break;
264266
+ if (b0 <= 127) {
264267
+ output += String.fromCodePoint(b0);
264268
+ i += 1;
264269
+ continue;
264270
+ }
264271
+ if (b0 >= 194 && b0 <= 223) {
264272
+ const b1 = data[i + 1];
264273
+ if (b1 !== void 0 && isUtf8Continuation(b1)) {
264274
+ output += String.fromCodePoint((b0 & 31) << 6 | b1 & 63);
264275
+ i += 2;
264276
+ continue;
264277
+ }
264278
+ i += 1;
264279
+ continue;
264280
+ }
264281
+ if (b0 >= 224 && b0 <= 239) {
264282
+ const b1 = data[i + 1];
264283
+ const b2 = data[i + 2];
264284
+ 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)) {
264285
+ output += String.fromCodePoint((b0 & 15) << 12 | (b1 & 63) << 6 | b2 & 63);
264286
+ i += 3;
264287
+ continue;
264288
+ }
264289
+ i += 1;
264290
+ continue;
264291
+ }
264292
+ if (b0 >= 240 && b0 <= 244) {
264293
+ const b1 = data[i + 1];
264294
+ const b2 = data[i + 2];
264295
+ const b3 = data[i + 3];
264296
+ 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)) {
264297
+ output += String.fromCodePoint((b0 & 7) << 18 | (b1 & 63) << 12 | (b2 & 63) << 6 | b3 & 63);
264298
+ i += 4;
264299
+ continue;
264300
+ }
264301
+ i += 1;
264302
+ continue;
264303
+ }
264304
+ i += 1;
264305
+ }
264306
+ return output;
264258
264307
  }
264259
- function blunThinkingIntentInput(input) {
264260
- const channelText = [...blunExtractText(input).matchAll(/(?:^|\r?\n)<channel\b[^>]*>\r?\n([\s\S]*?)\r?\n<\/channel>(?=\r?\n|$)/gi)].at(-1)?.[1];
264261
- return channelText === void 0 ? input : [{
264262
- type: "text",
264263
- text: channelText
264264
- }];
264308
+ function decodeUtf16LeIgnore(data) {
264309
+ let output = "";
264310
+ let i = 0;
264311
+ while (i + 1 < data.length) {
264312
+ const first = data[i];
264313
+ const second = data[i + 1];
264314
+ if (first === void 0 || second === void 0) break;
264315
+ const codeUnit = first | second << 8;
264316
+ if (codeUnit >= 55296 && codeUnit <= 56319) {
264317
+ const lowFirst = data[i + 2];
264318
+ const lowSecond = data[i + 3];
264319
+ if (lowFirst !== void 0 && lowSecond !== void 0) {
264320
+ const low = lowFirst | lowSecond << 8;
264321
+ if (low >= 56320 && low <= 57343) {
264322
+ const codePoint = 65536 + (codeUnit - 55296 << 10) + (low - 56320);
264323
+ output += String.fromCodePoint(codePoint);
264324
+ i += 4;
264325
+ continue;
264326
+ }
264327
+ }
264328
+ i += 2;
264329
+ continue;
264330
+ }
264331
+ if (codeUnit >= 56320 && codeUnit <= 57343) {
264332
+ i += 2;
264333
+ continue;
264334
+ }
264335
+ output += String.fromCodePoint(codeUnit);
264336
+ i += 2;
264337
+ }
264338
+ return output;
264265
264339
  }
264266
- function blunTurnHasAttachment(input) {
264267
- return input.some((part) => part.type !== "text") || /\b(?:image_path|attachment_file_id)\s*=/i.test(blunExtractText(input));
264340
+ /**
264341
+ * Decode a Buffer into a string with Python-compatible `errors` handling.
264342
+ *
264343
+ * - `'strict'` (default): throw on invalid sequences (via TextDecoder `fatal: true`)
264344
+ * - `'replace'`: substitute each invalid sequence with U+FFFD (TextDecoder default)
264345
+ * - `'ignore'`: drop invalid input sequences while preserving valid U+FFFD characters
264346
+ *
264347
+ * Falls back to `Buffer.toString(encoding)` for encodings TextDecoder does not
264348
+ * support (e.g. `hex`, `base64`, `binary`, `latin1`) — those are lossless
264349
+ * byte-to-character mappings so `errors` has no effect.
264350
+ * @internal
264351
+ */
264352
+ function decodeTextWithErrors(data, encoding, errors = "strict", ignoreBOM = false) {
264353
+ let webLabel;
264354
+ switch (encoding) {
264355
+ case "utf-8":
264356
+ case "utf8":
264357
+ webLabel = "utf-8";
264358
+ break;
264359
+ case "utf16le":
264360
+ case "ucs2":
264361
+ case "ucs-2":
264362
+ webLabel = "utf-16le";
264363
+ break;
264364
+ default: webLabel = void 0;
264365
+ }
264366
+ if (webLabel === void 0) return data.toString(encoding);
264367
+ if (errors === "strict") return new TextDecoder(webLabel, {
264368
+ fatal: true,
264369
+ ignoreBOM
264370
+ }).decode(data);
264371
+ if (errors === "ignore") return webLabel === "utf-8" ? decodeUtf8Ignore(data) : decodeUtf16LeIgnore(data);
264372
+ return new TextDecoder(webLabel, {
264373
+ fatal: false,
264374
+ ignoreBOM
264375
+ }).decode(data);
264268
264376
  }
264269
- function greetingHistory(history, anchor) {
264270
- const start = anchor === void 0 ? -1 : history.indexOf(anchor);
264271
- if (start < 0) return history;
264272
- const recent = [];
264273
- let chars = 0;
264274
- for (let i = start - 1; i >= 0 && recent.length < 6; i -= 1) {
264275
- const message = history[i];
264276
- if (message.role === "tool" || message.toolCalls.length > 0) break;
264277
- if (message.role !== "assistant" && message.origin?.kind !== "user") continue;
264278
- const size = blunExtractText(message.content).length;
264279
- if (chars + size > 12e3 || message.content.some((part) => part.type !== "text")) break;
264280
- recent.unshift(message);
264281
- chars += size;
264377
+ /**
264378
+ * Convert a glob pattern segment (e.g. "*.txt", "file?.log") into a RegExp.
264379
+ * Mirrors Python pathlib behavior: includes dotfiles, case-sensitive by default.
264380
+ * @internal
264381
+ */
264382
+ function globPatternToRegex(pattern, caseSensitive) {
264383
+ let regex = "^";
264384
+ for (let i = 0; i < pattern.length; i++) {
264385
+ const ch = pattern[i];
264386
+ if (ch === void 0) break;
264387
+ switch (ch) {
264388
+ case "*":
264389
+ regex += "[^/]*";
264390
+ break;
264391
+ case "?":
264392
+ regex += "[^/]";
264393
+ break;
264394
+ case "[": {
264395
+ const end = pattern.indexOf("]", i + 1);
264396
+ if (end === -1) regex += "\\[";
264397
+ else {
264398
+ let charClass = pattern.slice(i + 1, end);
264399
+ charClass = charClass.replace(/\\/g, "\\\\");
264400
+ if (charClass.startsWith("!")) charClass = "^" + charClass.slice(1);
264401
+ else if (charClass.startsWith("^")) charClass = "\\" + charClass;
264402
+ regex += "[" + charClass + "]";
264403
+ i = end;
264404
+ }
264405
+ break;
264406
+ }
264407
+ case "\\":
264408
+ if (i + 1 < pattern.length) {
264409
+ const next = pattern.charAt(i + 1);
264410
+ regex += next.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
264411
+ i++;
264412
+ } else regex += "\\\\";
264413
+ break;
264414
+ default: regex += ch.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
264415
+ }
264282
264416
  }
264283
- while (recent.length > 0 && recent[0].role !== "user") recent.shift();
264284
- return [...recent, ...history.slice(start)];
264417
+ regex += "$";
264418
+ return new RegExp(regex, caseSensitive ? "" : "i");
264285
264419
  }
264286
264420
  /**
264287
- * BLUN: builtin tools kept when the full schema set would overflow a small
264288
- * context window. The essential read / edit / run / track loop — enough for
264289
- * King to do real work on a 32k window without the full ~15k-token tool floor
264290
- * that pushes a single task turn to ~82% context and into a compaction
264291
- * death-loop (compaction can only shrink conversation, never the fixed tool
264292
- * floor, so it re-triggers every turn).
264421
+ * A Readable wrapper that preserves source backpressure while still allowing
264422
+ * consumers to read buffered output after the source has ended.
264423
+ * @internal
264293
264424
  */
264294
- const BLUN_LEAN_TOOL_NAMES = new Set([
264295
- "Read",
264296
- "ReadBatch",
264297
- "Write",
264298
- "Edit",
264299
- "Grep",
264300
- "Glob",
264301
- "Bash",
264302
- "TodoList",
264303
- "Skill",
264304
- "CompactConversation",
264305
- "MistakeRecord"
264306
- ]);
264307
- const BLUN_LANGUAGE_REVIEW_TOOL_NAMES = new Set([
264308
- "release_response",
264309
- "release_translation",
264310
- "verify_release_token",
264311
- "validate_text"
264312
- ].map((name) => qualifyMcpToolName("blun-language-guard", name)));
264425
+ var BufferedReadable = class extends Readable {
264426
+ _source;
264427
+ _ended = false;
264428
+ constructor(source) {
264429
+ super({ highWaterMark: 128 * 1024 });
264430
+ this._source = source;
264431
+ this._source.on("data", this._onData);
264432
+ this._source.on("end", this._onEnd);
264433
+ this._source.on("close", this._onClose);
264434
+ this._source.on("error", this._onError);
264435
+ }
264436
+ _read() {
264437
+ if (!this._ended && !this.destroyed) this._source.resume();
264438
+ }
264439
+ _destroy(error, callback) {
264440
+ this._source.off("data", this._onData);
264441
+ this._source.off("end", this._onEnd);
264442
+ this._source.off("close", this._onClose);
264443
+ this._source.off("error", this._onError);
264444
+ this._source.destroy();
264445
+ callback(error);
264446
+ }
264447
+ _onData = (chunk) => {
264448
+ if (!this.push(chunk)) this._source.pause();
264449
+ };
264450
+ _onEnd = () => {
264451
+ this._ended = true;
264452
+ this.push(null);
264453
+ };
264454
+ _onClose = () => {
264455
+ if (!this._ended) {
264456
+ this._ended = true;
264457
+ this.push(null);
264458
+ }
264459
+ };
264460
+ _onError = (error) => {
264461
+ this.destroy(error);
264462
+ };
264463
+ };
264464
+ //#endregion
264465
+ //#region ../../packages/kaos/src/login-shell-path.ts
264313
264466
  /**
264314
- * Channel/session MCP tools that must survive the lean cut — e.g. the Telegram
264315
- * reply tools. Without these a bridged session can RECEIVE a message but has no
264316
- * way to answer it (King processes the turn, then has no reply tool). Their
264317
- * schemas are tiny, so keeping them does not reappear the compaction floor.
264318
- * Matches both bare names and MCP-prefixed variants (…__reply).
264467
+ * Login-shell PATH probe — enrich `process.env.PATH` with entries from the
264468
+ * user's login shell.
264469
+ *
264470
+ * When blun is launched from a context that skipped the user's shell
264471
+ * profile (GUI launchers, non-login parent shells), `process.env.PATH`
264472
+ * misses entries like `/opt/homebrew/bin`, so commands spawned by the Bash
264473
+ * tool can't find tools the user has in their interactive shell (e.g.
264474
+ * `gh`). We run the user's login shell once (`$SHELL -l -c /usr/bin/env`),
264475
+ * extract its PATH, and append the entries the current PATH lacks. Existing
264476
+ * entries keep their order and priority; failures (no resolvable shell,
264477
+ * hung or broken profile) silently leave PATH untouched.
264319
264478
  *
264479
+ * launchd/daemon launches can leave `$SHELL` unset or blank (see
264480
+ * `defaultShell()` in agent-core's terminalService for the same case), so
264481
+ * the probe falls back to the OS account's login shell from the user
264482
+ * database before giving up.
264483
+ *
264484
+ * Like `detectEnvironment`, the probe is a pure function of injected deps
264485
+ * so the suite runs identically on any host. Windows is skipped: the
264486
+ * problem is specific to POSIX login-shell profiles.
264320
264487
  */
264321
- const BLUN_LEAN_KEEP_RE = /(^|__|:)(reply|react|edit_message|download_attachment|memory_(?:status|settings_update|remember|list|threads_list|recall))$/i;
264322
- /** Telegram writes require a real inbound user turn, never an internal nudge. */
264323
- const BLUN_TELEGRAM_OUTBOUND_TOOL_RE = /^mcp__[^\s]*telegram[^\s]*__(?:reply|react|edit_message)$/i;
264324
- function blunToolsForOrigin(tools, origin) {
264325
- if (origin.kind !== "system_trigger" && origin.kind !== "injection") return tools;
264326
- return tools.filter((tool) => !BLUN_TELEGRAM_OUTBOUND_TOOL_RE.test(tool.name));
264488
+ const LOGIN_SHELL_ENV_TIMEOUT_MS = 5e3;
264489
+ /**
264490
+ * Run the user's login shell and return its PATH, or `undefined` when the
264491
+ * probe does not apply (Windows, no resolvable shell) or fails (spawn
264492
+ * error, timeout, no PATH in the output).
264493
+ */
264494
+ async function probeLoginShellPath(deps) {
264495
+ if (deps.platform === "win32") return void 0;
264496
+ const envShell = deps.env["SHELL"]?.trim();
264497
+ const shell = envShell === void 0 || envShell.length === 0 ? deps.userShell() : envShell;
264498
+ if (shell === void 0 || shell.length === 0) return void 0;
264499
+ const stdout = await deps.execFileText(shell, [
264500
+ "-l",
264501
+ "-c",
264502
+ "/usr/bin/env"
264503
+ ], LOGIN_SHELL_ENV_TIMEOUT_MS);
264504
+ if (stdout === void 0) return void 0;
264505
+ let path;
264506
+ for (const line of stdout.split("\n")) if (line.startsWith("PATH=")) path = line.slice(5).trim();
264507
+ if (path === void 0 || path.length === 0) return void 0;
264508
+ return path;
264327
264509
  }
264328
264510
  /**
264329
- * Restore bounded schema disclosure even on large-context models. Only tools
264330
- * already eligible for this turn enter the loader; execution still follows
264331
- * the ordinary permission pipeline.
264511
+ * Union of the current PATH and the login-shell PATH: the current PATH
264512
+ * string is kept verbatim — including empty components, which POSIX
264513
+ * command lookup treats as the current directory — and login-shell
264514
+ * entries the current PATH lacks are appended in their own order. When
264515
+ * nothing is missing the current string is returned unchanged. Only
264516
+ * absolute login-shell entries are imported: empty, `.`, and relative
264517
+ * components are all cwd-dependent lookup, and appending one the user
264518
+ * did not already have would widen their search path — LocalKaos runs
264519
+ * commands from arbitrary workspace directories.
264332
264520
  */
264333
- function blunSelectTurnTools(tools, maxContextTokens, loadedToolNames = /* @__PURE__ */ new Set(), requiredToolNames = /* @__PURE__ */ new Set(), selected = []) {
264334
- if (estimateTokensForTools(tools) <= (0, import_turn_tool_performance_policy.toolSchemaBudgetTokens)(maxContextTokens) || tools.some((tool) => tool.name === "ToolSearch")) {
264335
- selected.splice(0, selected.length, ...tools);
264336
- return selected;
264521
+ function mergeLoginShellPath(currentPath, loginShellPath) {
264522
+ const current = currentPath ?? "";
264523
+ const seen = new Set(current.split(":").filter((entry) => entry.length > 0));
264524
+ const additions = [];
264525
+ for (const entry of loginShellPath.split(":")) {
264526
+ if (!entry.startsWith("/") || seen.has(entry)) continue;
264527
+ seen.add(entry);
264528
+ additions.push(entry);
264337
264529
  }
264338
- selected.splice(0, selected.length, ...tools.filter((tool) => BLUN_LEAN_TOOL_NAMES.has(tool.name) || BLUN_LEAN_KEEP_RE.test(tool.name) || BLUN_LANGUAGE_REVIEW_TOOL_NAMES.has(tool.name) || loadedToolNames.has(tool.name) || requiredToolNames.has(tool.name)));
264339
- const names = new Set(selected.map((tool) => tool.name));
264340
- const deferred = tools.filter((tool) => !names.has(tool.name));
264341
- if (deferred.length > 0) selected.push((0, import_turn_tool_performance_policy.createDeferredToolLoader)(selected, deferred, loadedToolNames));
264342
- return selected;
264530
+ if (additions.length === 0) return current;
264531
+ if (currentPath === void 0) return additions.join(":");
264532
+ return `${current}:${additions.join(":")}`;
264343
264533
  }
264344
- const LLM_NOT_SET_MESSAGE = "LLM not set, send \"/login\" to login";
264345
- /** Origin tag for the synthetic "continue" prompt that drives each goal turn. */
264346
- const GOAL_CONTINUATION_ORIGIN = {
264347
- kind: "system_trigger",
264348
- name: "goal_continuation"
264349
- };
264350
- function goalContinuationOrigin(origin) {
264351
- const cronRunId = origin.kind === "cron_job" ? origin.runId : origin.kind === "system_trigger" && origin.name === "goal_continuation" ? origin.cronRunId : void 0;
264352
- return cronRunId === void 0 ? GOAL_CONTINUATION_ORIGIN : {
264353
- kind: "system_trigger",
264354
- name: "goal_continuation",
264355
- cronRunId
264356
- };
264534
+ /** Probe the login shell and merge its PATH into `deps.env['PATH']`. */
264535
+ async function applyLoginShellPath(deps) {
264536
+ const loginShellPath = await probeLoginShellPath(deps);
264537
+ if (loginShellPath === void 0) return;
264538
+ const currentPath = deps.env["PATH"];
264539
+ const merged = mergeLoginShellPath(currentPath, loginShellPath);
264540
+ if (merged === (currentPath ?? "")) return;
264541
+ deps.env["PATH"] = merged;
264357
264542
  }
264358
- const GOAL_COMPLETION_REMINDER_NAME = "goal_completion";
264359
- const GOAL_BLOCKED_REMINDER_NAME = "goal_blocked";
264360
- const GOAL_RATE_LIMIT_PAUSE_REASON = "Paused after provider rate limit";
264361
- const GOAL_PROVIDER_CONNECTION_PAUSE_PREFIX = "Paused after provider connection error";
264362
- const GOAL_PROVIDER_AUTH_PAUSE_PREFIX = "Paused after provider authentication error";
264363
- const GOAL_PROVIDER_API_PAUSE_PREFIX = "Paused after provider API error";
264364
- const GOAL_MODEL_CONFIG_PAUSE_PREFIX = "Paused after model configuration error";
264365
- const GOAL_RUNTIME_PAUSE_PREFIX = "Paused after runtime error";
264366
- const GOAL_PROVIDER_FILTERED_PAUSE_REASON = "Paused after provider safety policy block";
264367
264543
  /**
264368
- * The prompt the goal driver appends to start each continuation turn — the
264369
- * autonomous stand-in for the user typing "continue". The model decides when to
264370
- * stop by calling `UpdateGoal`; otherwise the driver runs another turn.
264544
+ * Production convenience — apply the probe to `process.env` once per
264545
+ * process. Memoised like `detectEnvironmentFromNode`: the login-shell PATH
264546
+ * does not change for the lifetime of the process, and repeated
264547
+ * `LocalKaos.create()` calls must not re-spawn the shell.
264371
264548
  */
264372
- const GOAL_CONTINUATION_PROMPT = [
264373
- "Continue working toward the active goal.",
264374
- "Keep the self-audit brief. Do not explore unrelated interpretations once the goal can be",
264375
- "decided. If the objective is simple, already answered, impossible, unsafe, or contradictory,",
264376
- "do not run another goal turn. Explain briefly if useful, then call UpdateGoal with `complete`",
264377
- "or `blocked` in the same turn. Otherwise, weigh the objective and any completion criteria",
264378
- "against the work done so far. Goal mode is iterative: do one coherent slice of work, then",
264379
- "reassess.",
264380
- "",
264381
- "CRITICAL — WHEN TO END THE GOAL:",
264382
- "When all required work is done — files written, code executed, tests passing, validation",
264383
- "passed — you MUST call UpdateGoal with `complete` IMMEDIATELY. Do not narrerate the next step",
264384
- "and stop without calling it. Do not leave the goal active when the work is finished.",
264385
- "If you have completed the task and are about to end your response, call UpdateGoal with",
264386
- "`complete` FIRST, then give your summary. Never end a turn with the goal still active if",
264387
- "the work is actually done.",
264388
- "",
264389
- "Do not mark complete after only producing a plan, summary, first pass, or partial result.",
264390
- "Do not mark complete after announcing the next step without executing it.",
264391
- "If an external condition or required user input prevents progress, or the objective cannot",
264392
- "be completed as stated, call UpdateGoal with `blocked`. On error or failure, call",
264393
- "UpdateGoal with `blocked`, NOT `complete`.",
264394
- "Otherwise keep going — use the existing conversation context and your tools,",
264395
- "and do not ask the user for input unless a real blocker prevents progress."
264396
- ].join(" ");
264397
- var TurnFlow = class {
264398
- agent;
264399
- admissionClosed = false;
264400
- loadedToolNames = /* @__PURE__ */ new Set();
264401
- refreshSelectedTools;
264402
- executions = new ExecutionLedger();
264403
- executingControllers = /* @__PURE__ */ new Set();
264404
- steerBuffer = [];
264405
- nextSteerSequence = 0;
264406
- turnId = -1;
264407
- activeTurn = null;
264408
- toolCallStartedAt = /* @__PURE__ */ new Map();
264409
- toolCallDupType = /* @__PURE__ */ new Map();
264410
- stepToolCallKeys = /* @__PURE__ */ new Map();
264411
- telemetryModeByTurn = /* @__PURE__ */ new Map();
264412
- currentStepByTurn = /* @__PURE__ */ new Map();
264413
- interruptedTelemetryTurnIds = /* @__PURE__ */ new Set();
264414
- stepFailureByTurn = /* @__PURE__ */ new Map();
264415
- providerRequestFailureByTurn = /* @__PURE__ */ new Map();
264416
- currentStep = 0;
264417
- constructor(agent) {
264418
- this.agent = agent;
264549
+ /**
264550
+ * Login shell from the OS user database (`/etc/passwd` via getpwuid on
264551
+ * Linux, Directory Services on macOS). `userInfo()` throws when the uid
264552
+ * has no database entry (e.g. containers running an arbitrary uid), and
264553
+ * service accounts may carry `/usr/sbin/nologin` — the latter needs no
264554
+ * special casing here because probing it simply fails and degrades
264555
+ * silently.
264556
+ */
264557
+ function userShellFromNode() {
264558
+ try {
264559
+ const shell = userInfo().shell;
264560
+ return shell === null || shell.length === 0 ? void 0 : shell;
264561
+ } catch {
264562
+ return;
264419
264563
  }
264420
- /** Explicit host shutdown only; ordinary cancellation remains resumable. */
264421
- closeAdmission() {
264422
- this.admissionClosed = true;
264564
+ }
264565
+ let appliedLoginShellPath;
264566
+ function applyLoginShellPathFromNode() {
264567
+ if (appliedLoginShellPath !== void 0) return appliedLoginShellPath;
264568
+ appliedLoginShellPath = applyLoginShellPath({
264569
+ platform: process.platform,
264570
+ env: process.env,
264571
+ userShell: userShellFromNode,
264572
+ execFileText
264573
+ });
264574
+ return appliedLoginShellPath;
264575
+ }
264576
+ //#endregion
264577
+ //#region ../../packages/kaos/src/local.ts
264578
+ const isWindows$1 = process.platform === "win32";
264579
+ const READ_CHUNK_SIZE = 64 * 1024;
264580
+ /**
264581
+ * Build the `(dev, ino)` cycle-detection key used by `_globWalk`'s
264582
+ * visited set. Returns `null` when `ino` is 0, which Node returns on
264583
+ * filesystems that don't carry inodes (Windows FAT/exFAT, some SMB/NFS
264584
+ * mounts). A null key signals "no reliable identity for this dir" so
264585
+ * the caller skips visited tracking for that descent — cycle safety
264586
+ * is weakened on those filesystems, but normal walking works instead
264587
+ * of every directory colliding on the shared key `"<dev>:0"`.
264588
+ */
264589
+ function cycleKey(s) {
264590
+ if (s.ino === 0) return null;
264591
+ return `${String(s.dev)}:${String(s.ino)}`;
264592
+ }
264593
+ function buildLocalSpawnOptions(_isWindows, cwd, env) {
264594
+ return {
264595
+ cwd,
264596
+ env,
264597
+ stdio: [
264598
+ "pipe",
264599
+ "pipe",
264600
+ "pipe"
264601
+ ],
264602
+ detached: true,
264603
+ windowsHide: true
264604
+ };
264605
+ }
264606
+ var LocalProcess = class {
264607
+ stdin;
264608
+ stdout;
264609
+ stderr;
264610
+ pid;
264611
+ _child;
264612
+ _exitCode = null;
264613
+ _exitPromise;
264614
+ _disposed = false;
264615
+ constructor(child) {
264616
+ if (child.stdin === null || child.stdout === null || child.stderr === null) throw new Error("Process must be created with stdin/stdout/stderr pipes.");
264617
+ this._child = child;
264618
+ this.stdin = child.stdin;
264619
+ this.stdout = new BufferedReadable(child.stdout);
264620
+ this.stderr = new BufferedReadable(child.stderr);
264621
+ this.pid = child.pid ?? -1;
264622
+ this._exitPromise = new Promise((resolve, reject) => {
264623
+ child.on("exit", (code) => {
264624
+ this._exitCode = code ?? -1;
264625
+ resolve(this._exitCode);
264626
+ });
264627
+ child.on("error", (error) => {
264628
+ reject(error);
264629
+ });
264630
+ });
264423
264631
  }
264424
- async quiesce(options) {
264425
- const deadline = new QuiescenceDeadline(options.deadlineAt);
264426
- this.closeAdmission();
264427
- const reason = /* @__PURE__ */ new Error("Turn admission is closed for quiescence");
264428
- if (this.hasActiveTurn) this.cancel(void 0, reason);
264429
- for (const controller of this.executingControllers) controller.abort(reason);
264430
- await deadline.wait(this.executions.drain());
264431
- const checkedAt = Date.now();
264432
- const remaining = this.executions.pending;
264433
- return {
264434
- admissionClosed: true,
264435
- checkedAt,
264436
- remaining,
264437
- status: deadline.expired || remaining.length > 0 ? "incomplete" : "quiescent",
264438
- reason: deadline.expired ? "deadline" : void 0
264439
- };
264632
+ get exitCode() {
264633
+ return this._exitCode;
264440
264634
  }
264441
- /** Actual execution, unlike waitForCurrentTurn(signal), is not abortable. */
264442
- waitForExecutionEnd() {
264443
- return this.executions.drain();
264635
+ async wait() {
264636
+ return this._exitPromise;
264444
264637
  }
264445
- get pendingExecutions() {
264446
- return this.executions.pending;
264638
+ kill(signal) {
264639
+ if (this.pid <= 0) return Promise.resolve();
264640
+ if (isWindows$1) {
264641
+ const taskkillArgs = [
264642
+ "/T",
264643
+ "/F",
264644
+ "/PID",
264645
+ String(this.pid)
264646
+ ];
264647
+ return new Promise((resolve) => {
264648
+ const killer = spawn("taskkill", taskkillArgs, {
264649
+ stdio: "ignore",
264650
+ windowsHide: true
264651
+ });
264652
+ const done = () => {
264653
+ resolve();
264654
+ };
264655
+ killer.once("error", done);
264656
+ killer.once("close", done);
264657
+ });
264658
+ }
264659
+ try {
264660
+ process.kill(-this.pid, signal ?? "SIGTERM");
264661
+ } catch (error) {
264662
+ const err = error;
264663
+ if (err.code === "ESRCH") return Promise.resolve();
264664
+ if (err.code === "EPERM") {
264665
+ try {
264666
+ this._child.kill(signal ?? "SIGTERM");
264667
+ } catch {}
264668
+ return Promise.resolve();
264669
+ }
264670
+ throw error;
264671
+ }
264672
+ return Promise.resolve();
264447
264673
  }
264448
- assertAdmissionOpen() {
264449
- if (this.admissionClosed) throw new Error("Turn admission is closed for quiescence");
264674
+ dispose() {
264675
+ if (this._disposed) return;
264676
+ this._disposed = true;
264677
+ this.stdin.destroy();
264678
+ this.stdout.destroy();
264679
+ this.stderr.destroy();
264450
264680
  }
264451
- /** Best-effort agent id (main / generated id) derived from the agent homedir. */
264681
+ };
264682
+ /**
264683
+ * A KAOS implementation that directly interacts with the local filesystem.
264684
+ *
264685
+ * Note: LocalKaos maintains its own per-instance working directory (`_cwd`)
264686
+ * rather than mutating `process.cwd()`. This lets multiple LocalKaos instances
264687
+ * coexist with independent cwds (e.g. when switching contexts via
264688
+ * `runWithKaos`) without cross-polluting each other's relative-path resolution.
264689
+ */
264690
+ var LocalKaos = class LocalKaos {
264691
+ name = "local";
264692
+ osEnv;
264693
+ _cwd;
264694
+ _envLayers;
264695
+ constructor(osEnv, cwd, envLayers = []) {
264696
+ this._cwd = normalize$3(cwd ?? process.cwd());
264697
+ this.osEnv = osEnv;
264698
+ this._envLayers = envLayers;
264699
+ }
264700
+ /**
264701
+ * Construct a fresh `LocalKaos` after probing the host environment.
264702
+ *
264703
+ * Each call returns a new instance with its own `_cwd`; concurrent
264704
+ * callers can therefore operate on independent working directories
264705
+ * without polluting one another.
264706
+ */
264707
+ static async create() {
264708
+ const [osEnv] = await Promise.all([detectEnvironmentFromNode(), applyLoginShellPathFromNode()]);
264709
+ return new LocalKaos(osEnv);
264710
+ }
264711
+ withCwd(cwd) {
264712
+ return new LocalKaos(this.osEnv, cwd, this._envLayers);
264713
+ }
264714
+ withEnv(env) {
264715
+ return new LocalKaos(this.osEnv, this._cwd, [...this._envLayers, env]);
264716
+ }
264717
+ _resolvePath(path) {
264718
+ if (isAbsolute$1(path)) return normalize$3(path);
264719
+ return join$1(this._cwd, path);
264720
+ }
264721
+ pathClass() {
264722
+ return isWindows$1 ? "win32" : "posix";
264723
+ }
264724
+ normpath(path) {
264725
+ return normalize$3(path);
264726
+ }
264727
+ gethome() {
264728
+ return normalize$3(homedir());
264729
+ }
264730
+ getcwd() {
264731
+ return this._cwd;
264732
+ }
264733
+ /**
264734
+ * Change the working directory of this LocalKaos instance.
264735
+ *
264736
+ * Unlike Python's `os.chdir`, this is instance-scoped and never touches
264737
+ * `process.cwd()`. Child processes spawned via {@link exec} inherit this
264738
+ * instance's `_cwd`; concurrent LocalKaos instances each carry their own
264739
+ * independent cwd. If you need Python-compatible process-global cwd,
264740
+ * call `process.chdir(x)` directly.
264741
+ */
264742
+ async chdir(path) {
264743
+ const resolved = this._resolvePath(path);
264744
+ if (!(await stat(resolved)).isDirectory()) throw new Error(`Not a directory: ${resolved}`);
264745
+ this._cwd = resolved;
264746
+ }
264747
+ async stat(path, options) {
264748
+ const resolved = this._resolvePath(path);
264749
+ const s = options?.followSymlinks ?? true ? await stat(resolved) : await lstat(resolved);
264750
+ return {
264751
+ stMode: s.mode,
264752
+ stIno: s.ino,
264753
+ stDev: s.dev,
264754
+ stNlink: s.nlink,
264755
+ stUid: s.uid,
264756
+ stGid: s.gid,
264757
+ stSize: s.size,
264758
+ stAtime: s.atimeMs / 1e3,
264759
+ stMtime: s.mtimeMs / 1e3,
264760
+ stCtime: isWindows$1 ? s.birthtimeMs / 1e3 : s.ctimeMs / 1e3
264761
+ };
264762
+ }
264763
+ async *iterdir(path) {
264764
+ const resolved = this._resolvePath(path);
264765
+ const entries = await readdir(resolved);
264766
+ for (const entry of entries) yield join$1(resolved, entry);
264767
+ }
264768
+ async *glob(path, pattern, options) {
264769
+ const resolved = this._resolvePath(path);
264770
+ const caseSensitive = options?.caseSensitive ?? true;
264771
+ const patternParts = pattern.split("/");
264772
+ const initVisited = /* @__PURE__ */ new Set();
264773
+ try {
264774
+ const rootKey = cycleKey(await stat(resolved));
264775
+ if (rootKey !== null) initVisited.add(rootKey);
264776
+ } catch {}
264777
+ yield* this._globWalk(resolved, patternParts, caseSensitive, initVisited);
264778
+ }
264779
+ async *_globWalk(basePath, patternParts, caseSensitive, visited) {
264780
+ if (patternParts.length === 0) return;
264781
+ const [currentPattern, ...remainingParts] = patternParts;
264782
+ if (currentPattern === "**") {
264783
+ if (remainingParts.length > 0) yield* this._globWalk(basePath, remainingParts, caseSensitive, visited);
264784
+ else yield basePath;
264785
+ let entries;
264786
+ try {
264787
+ entries = await readdir(basePath);
264788
+ } catch {
264789
+ return;
264790
+ }
264791
+ for (const entry of entries) {
264792
+ const fullPath = join$1(basePath, entry);
264793
+ let entryStat;
264794
+ try {
264795
+ entryStat = await stat(fullPath);
264796
+ } catch {
264797
+ continue;
264798
+ }
264799
+ if (entryStat.isDirectory()) {
264800
+ const key = cycleKey(entryStat);
264801
+ if (key !== null && visited.has(key)) continue;
264802
+ yield* this._globWalk(fullPath, patternParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
264803
+ } else if (remainingParts.length === 0) yield fullPath;
264804
+ }
264805
+ } else {
264806
+ const regex = globPatternToRegex(currentPattern ?? "", caseSensitive);
264807
+ let entries;
264808
+ try {
264809
+ entries = await readdir(basePath);
264810
+ } catch {
264811
+ return;
264812
+ }
264813
+ for (const entry of entries) {
264814
+ if (!regex.test(entry)) continue;
264815
+ const fullPath = join$1(basePath, entry);
264816
+ if (remainingParts.length === 0) yield fullPath;
264817
+ else {
264818
+ let entryStat;
264819
+ try {
264820
+ entryStat = await stat(fullPath);
264821
+ } catch {
264822
+ continue;
264823
+ }
264824
+ if (entryStat.isDirectory()) {
264825
+ const key = cycleKey(entryStat);
264826
+ if (key !== null && visited.has(key)) continue;
264827
+ yield* this._globWalk(fullPath, remainingParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
264828
+ }
264829
+ }
264830
+ }
264831
+ }
264832
+ }
264833
+ async readBytes(path, n) {
264834
+ const resolved = this._resolvePath(path);
264835
+ if (n === void 0) return Buffer.from(await readFile(resolved));
264836
+ const fh = await open(resolved, "r");
264837
+ try {
264838
+ const buf = Buffer.alloc(n);
264839
+ const { bytesRead } = await fh.read(buf, 0, n, 0);
264840
+ return buf.subarray(0, bytesRead);
264841
+ } finally {
264842
+ await fh.close();
264843
+ }
264844
+ }
264845
+ async readText(path, options) {
264846
+ const resolved = this._resolvePath(path);
264847
+ const encoding = options?.encoding ?? "utf-8";
264848
+ const errors = options?.errors ?? "strict";
264849
+ return decodeTextWithErrors(await readFile(resolved), encoding, errors);
264850
+ }
264851
+ async *readLines(path, options) {
264852
+ const resolved = this._resolvePath(path);
264853
+ const encoding = options?.encoding ?? "utf-8";
264854
+ const errors = options?.errors ?? "strict";
264855
+ if (!isUtf8Encoding(encoding)) {
264856
+ yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readFile(resolved), encoding, errors));
264857
+ return;
264858
+ }
264859
+ yield* this._readUtf8Lines(resolved, errors);
264860
+ }
264861
+ async scanTextFile(path) {
264862
+ const fh = await open(this._resolvePath(path), "r");
264863
+ try {
264864
+ const buf = Buffer.alloc(READ_CHUNK_SIZE);
264865
+ const flags = {
264866
+ hasCrLf: false,
264867
+ hasLf: false,
264868
+ hasLoneCr: false
264869
+ };
264870
+ const validator = createUtf8Validator();
264871
+ let totalLines = 0;
264872
+ let totalBytes = 0;
264873
+ let endsWithNewline = false;
264874
+ let hasNul = false;
264875
+ let prevWasCr = false;
264876
+ while (true) {
264877
+ const { bytesRead } = await fh.read(buf, 0, buf.length, null);
264878
+ if (bytesRead === 0) break;
264879
+ const chunk = buf.subarray(0, bytesRead);
264880
+ validator.write(chunk);
264881
+ for (let i = 0; i < chunk.length; i += 1) {
264882
+ const byte = chunk[i];
264883
+ if (byte === void 0) continue;
264884
+ if (byte === 0) hasNul = true;
264885
+ if (byte === 10) totalLines += 1;
264886
+ }
264887
+ prevWasCr = updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr);
264888
+ totalBytes += bytesRead;
264889
+ endsWithNewline = chunk[bytesRead - 1] === 10;
264890
+ }
264891
+ if (prevWasCr) flags.hasLoneCr = true;
264892
+ validator.end();
264893
+ if (totalBytes > 0 && !endsWithNewline) totalLines += 1;
264894
+ return {
264895
+ totalLines,
264896
+ endsWithNewline,
264897
+ hasNul,
264898
+ lineEndingFlags: flags
264899
+ };
264900
+ } finally {
264901
+ await fh.close();
264902
+ }
264903
+ }
264904
+ async *readLineRange(path, options) {
264905
+ const resolved = this._resolvePath(path);
264906
+ const errors = options.errors ?? "strict";
264907
+ yield* this._readUtf8Lines(resolved, errors, {
264908
+ startLine: options.startLine,
264909
+ maxLines: options.maxLines
264910
+ });
264911
+ }
264912
+ async *readTailLines(path, options) {
264913
+ if (options.tailCount <= 0) return;
264914
+ const resolved = this._resolvePath(path);
264915
+ const errors = options.errors ?? "strict";
264916
+ const fh = await open(resolved, "r");
264917
+ try {
264918
+ const s = await fh.stat();
264919
+ if (s.size === 0) return;
264920
+ let pos = s.size;
264921
+ let foundLf = 0;
264922
+ let startOffset = 0;
264923
+ let needLf = options.tailCount;
264924
+ let sawTailBlock = false;
264925
+ while (pos > 0 && foundLf < needLf) {
264926
+ const readSize = Math.min(READ_CHUNK_SIZE, pos);
264927
+ pos -= readSize;
264928
+ const buf = Buffer.alloc(readSize);
264929
+ await fh.read(buf, 0, readSize, pos);
264930
+ if (!sawTailBlock) {
264931
+ sawTailBlock = true;
264932
+ needLf = buf[readSize - 1] === 10 ? options.tailCount + 1 : options.tailCount;
264933
+ }
264934
+ for (let i = readSize - 1; i >= 0; i -= 1) {
264935
+ if (buf[i] !== 10) continue;
264936
+ foundLf += 1;
264937
+ if (foundLf === needLf) {
264938
+ startOffset = pos + i + 1;
264939
+ break;
264940
+ }
264941
+ }
264942
+ }
264943
+ if (foundLf < needLf) startOffset = 0;
264944
+ yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readRange(fh, startOffset, s.size - startOffset), "utf-8", errors, startOffset !== 0));
264945
+ } finally {
264946
+ await fh.close();
264947
+ }
264948
+ }
264949
+ async *_readUtf8Lines(resolved, errors, range) {
264950
+ const startLine = range?.startLine ?? 1;
264951
+ const maxLines = range?.maxLines ?? Number.POSITIVE_INFINITY;
264952
+ const fh = await open(resolved, "r");
264953
+ try {
264954
+ const buf = Buffer.alloc(READ_CHUNK_SIZE);
264955
+ let pending = [];
264956
+ let pendingOffset = 0;
264957
+ let fileOffset = 0;
264958
+ let lineNo = 1;
264959
+ let yielded = 0;
264960
+ while (true) {
264961
+ const { bytesRead } = await fh.read(buf, 0, buf.length, null);
264962
+ if (bytesRead === 0) break;
264963
+ const chunk = buf.subarray(0, bytesRead);
264964
+ let lineStart = 0;
264965
+ for (let i = 0; i < chunk.length; i += 1) {
264966
+ if (chunk[i] !== 10) continue;
264967
+ const piece = chunk.subarray(lineStart, i + 1);
264968
+ const lineOffset = pending.length === 0 ? fileOffset + lineStart : pendingOffset;
264969
+ const line = pending.length === 0 ? piece : Buffer.concat([...pending, piece]);
264970
+ if (lineNo >= startLine) {
264971
+ yield decodeTextWithErrors(line, "utf-8", errors, lineOffset !== 0);
264972
+ yielded += 1;
264973
+ if (yielded >= maxLines) return;
264974
+ }
264975
+ pending = [];
264976
+ lineStart = i + 1;
264977
+ lineNo += 1;
264978
+ }
264979
+ if (lineStart < chunk.length) {
264980
+ const tail = Buffer.from(chunk.subarray(lineStart));
264981
+ if (pending.length === 0) pendingOffset = fileOffset + lineStart;
264982
+ pending.push(tail);
264983
+ }
264984
+ fileOffset += bytesRead;
264985
+ }
264986
+ if (pending.length > 0) {
264987
+ const line = Buffer.concat(pending);
264988
+ if (lineNo >= startLine) yield decodeTextWithErrors(line, "utf-8", errors, pendingOffset !== 0);
264989
+ }
264990
+ } finally {
264991
+ await fh.close();
264992
+ }
264993
+ }
264994
+ async writeBytes(path, data) {
264995
+ await writeFile(this._resolvePath(path), data);
264996
+ return data.length;
264997
+ }
264998
+ async writeText(path, data, options) {
264999
+ const resolved = this._resolvePath(path);
265000
+ const encoding = options?.encoding ?? "utf-8";
265001
+ if ((options?.mode ?? "w") === "a") await appendFile(resolved, data, encoding);
265002
+ else await writeFile(resolved, data, encoding);
265003
+ return data.length;
265004
+ }
265005
+ async mkdir(path, options) {
265006
+ const resolved = this._resolvePath(path);
265007
+ const parents = options?.parents ?? false;
265008
+ const existOk = options?.existOk ?? false;
265009
+ if (parents) {
265010
+ if (!existOk) try {
265011
+ if ((await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists`);
265012
+ } catch (error) {
265013
+ if (error instanceof KaosFileExistsError) throw error;
265014
+ if (error.code !== "ENOENT") throw error;
265015
+ }
265016
+ await mkdir(resolved, { recursive: true });
265017
+ return;
265018
+ }
265019
+ try {
265020
+ await mkdir(resolved);
265021
+ } catch (error) {
265022
+ if (existOk && error instanceof Error && "code" in error && error.code === "EEXIST") {
265023
+ if (!(await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists but is not a directory`);
265024
+ return;
265025
+ }
265026
+ throw error;
265027
+ }
265028
+ }
265029
+ async exec(...args) {
265030
+ const command = args[0];
265031
+ if (command === void 0) throw new Error("LocalKaos.exec(): at least one argument (the command to run) is required.");
265032
+ const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv()));
265033
+ await waitForSpawn(child);
265034
+ return new LocalProcess(child);
265035
+ }
265036
+ async execWithEnv(args, env) {
265037
+ const command = args[0];
265038
+ if (command === void 0) throw new Error("LocalKaos.execWithEnv(): at least one argument (the command to run) is required.");
265039
+ const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv(env)));
265040
+ await waitForSpawn(child);
265041
+ return new LocalProcess(child);
265042
+ }
265043
+ _buildExecEnv(invocationEnv) {
265044
+ if (this._envLayers.length === 0) return invocationEnv;
265045
+ const merged = {
265046
+ ...process.env,
265047
+ ...invocationEnv
265048
+ };
265049
+ for (const layer of this._envLayers) Object.assign(merged, layer);
265050
+ return merged;
265051
+ }
265052
+ };
265053
+ function isUtf8Encoding(encoding) {
265054
+ return encoding === "utf-8" || encoding === "utf8";
265055
+ }
265056
+ function* splitLinesKeepingTerminator(text) {
265057
+ if (text.length === 0) return;
265058
+ let start = 0;
265059
+ for (let i = 0; i < text.length; i += 1) if (text.codePointAt(i) === 10) {
265060
+ yield text.slice(start, i + 1);
265061
+ start = i + 1;
265062
+ }
265063
+ if (start < text.length) yield text.slice(start);
265064
+ }
265065
+ function updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr) {
265066
+ for (let i = 0; i < chunk.length; i += 1) {
265067
+ const byte = chunk[i];
265068
+ if (byte === void 0) continue;
265069
+ if (byte === 13) {
265070
+ if (prevWasCr) flags.hasLoneCr = true;
265071
+ prevWasCr = true;
265072
+ } else if (byte === 10) {
265073
+ if (prevWasCr) flags.hasCrLf = true;
265074
+ else flags.hasLf = true;
265075
+ prevWasCr = false;
265076
+ } else {
265077
+ if (prevWasCr) flags.hasLoneCr = true;
265078
+ prevWasCr = false;
265079
+ }
265080
+ }
265081
+ return prevWasCr;
265082
+ }
265083
+ function createUtf8Validator() {
265084
+ let needed = 0;
265085
+ let lower = 128;
265086
+ let upper = 191;
265087
+ const fail = () => {
265088
+ throw new TypeError("Invalid UTF-8 data");
265089
+ };
265090
+ return {
265091
+ write(chunk) {
265092
+ for (let i = 0; i < chunk.length; i += 1) {
265093
+ const byte = chunk[i];
265094
+ if (byte === void 0) continue;
265095
+ if (needed === 0) {
265096
+ if (byte <= 127) continue;
265097
+ if (byte >= 194 && byte <= 223) needed = 1;
265098
+ else if (byte === 224) {
265099
+ needed = 2;
265100
+ lower = 160;
265101
+ } else if (byte >= 225 && byte <= 236) needed = 2;
265102
+ else if (byte === 237) {
265103
+ needed = 2;
265104
+ upper = 159;
265105
+ } else if (byte >= 238 && byte <= 239) needed = 2;
265106
+ else if (byte === 240) {
265107
+ needed = 3;
265108
+ lower = 144;
265109
+ } else if (byte >= 241 && byte <= 243) needed = 3;
265110
+ else if (byte === 244) {
265111
+ needed = 3;
265112
+ upper = 143;
265113
+ } else fail();
265114
+ } else {
265115
+ if (byte < lower || byte > upper) fail();
265116
+ lower = 128;
265117
+ upper = 191;
265118
+ needed -= 1;
265119
+ }
265120
+ }
265121
+ },
265122
+ end() {
265123
+ if (needed !== 0) fail();
265124
+ }
265125
+ };
265126
+ }
265127
+ async function readRange(fh, start, length) {
265128
+ const data = Buffer.alloc(length);
265129
+ let offset = 0;
265130
+ while (offset < length) {
265131
+ const { bytesRead } = await fh.read(data, offset, length - offset, start + offset);
265132
+ if (bytesRead === 0) break;
265133
+ offset += bytesRead;
265134
+ }
265135
+ return offset === length ? data : data.subarray(0, offset);
265136
+ }
265137
+ function waitForSpawn(child) {
265138
+ return new Promise((resolve, reject) => {
265139
+ const onSpawn = () => {
265140
+ child.off("error", onError);
265141
+ resolve();
265142
+ };
265143
+ const onError = (err) => {
265144
+ child.off("spawn", onSpawn);
265145
+ reject(err);
265146
+ };
265147
+ child.once("spawn", onSpawn);
265148
+ child.once("error", onError);
265149
+ });
265150
+ }
265151
+ new AsyncLocalStorage();
265152
+ //#endregion
265153
+ //#region ../../packages/agent-core/src/agent/turn/historical-tool-projection.ts
265154
+ const MIN_CHARS = 3e3;
265155
+ const MAX_REFERENCE_CHARS = 2500;
265156
+ const RECOVERY_REFERENCE = /\[Tool result offloaded\]|\[Repeated tool result omitted\]|^output_path\s*:|Tool output exceeded \d+ characters/m;
265157
+ function identity(stat) {
265158
+ return `${stat.dev}:${stat.ino}:${stat.birthtimeNs}`;
265159
+ }
265160
+ function revision(stat) {
265161
+ return `${identity(stat)}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}:${stat.mode}`;
265162
+ }
265163
+ function canonical(path) {
265164
+ const normalized = resolve(path);
265165
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
265166
+ }
265167
+ async function checkedDirectory(path) {
265168
+ const stat = await lstat(path, { bigint: true });
265169
+ if (!stat.isDirectory() || stat.isSymbolicLink() || canonical(await realpath(path)) !== canonical(path)) throw new Error("Unsafe recovery directory");
265170
+ return stat;
265171
+ }
265172
+ async function checkedAncestors(path) {
265173
+ for (let current = resolve(path); current !== parse(current).root; current = dirname(current)) await checkedDirectory(current);
265174
+ }
265175
+ function persistableText(message) {
265176
+ if (message.isError === true || message.content.length !== 1) return void 0;
265177
+ const part = message.content[0];
265178
+ if (part?.type !== "text") return void 0;
265179
+ const text = part.text;
265180
+ 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;
265181
+ return text;
265182
+ }
265183
+ /** Lossless request-only view. The owning context and records never receive these references. */
265184
+ var HistoricalToolProjection = class {
265185
+ agent;
265186
+ files = /* @__PURE__ */ new Map();
265187
+ directories = /* @__PURE__ */ new Map();
265188
+ dirtyDirectories = /* @__PURE__ */ new Set();
265189
+ pending = Promise.resolve();
265190
+ constructor(agent) {
265191
+ this.agent = agent;
265192
+ }
265193
+ async project(history, turnAnchor, tools, context) {
265194
+ const unchanged = {
265195
+ history,
265196
+ changed: false
265197
+ };
265198
+ const boundary = turnAnchor === void 0 ? -1 : history.indexOf(turnAnchor);
265199
+ const read = tools.find((tool) => tool.name === "Read");
265200
+ if (boundary < 0 || !(read instanceof ReadTool) || !(this.agent.kaos instanceof LocalKaos) || this.agent.homedir === void 0 || this.agent.hooks?.hasMatchingHook("PreToolUse", "Read")) return unchanged;
265201
+ const calls = /* @__PURE__ */ new Map();
265202
+ const results = /* @__PURE__ */ new Map();
265203
+ const knownTools = new Set(this.agent.tools.loopTools.map((tool) => tool.name));
265204
+ history.forEach((message, index) => {
265205
+ for (const call of message.toolCalls) {
265206
+ const previous = calls.get(call.id);
265207
+ calls.set(call.id, {
265208
+ index,
265209
+ name: call.name,
265210
+ count: (previous?.count ?? 0) + 1
265211
+ });
265212
+ }
265213
+ if (message.role === "tool" && message.toolCallId !== void 0) {
265214
+ const previous = results.get(message.toolCallId);
265215
+ results.set(message.toolCallId, {
265216
+ index,
265217
+ count: (previous?.count ?? 0) + 1
265218
+ });
265219
+ }
265220
+ });
265221
+ const closedBatches = /* @__PURE__ */ new Set();
265222
+ for (let index = 0; index < boundary; index++) {
265223
+ const message = history[index];
265224
+ if (message.role === "assistant" && message.toolCalls.length > 0 && message.toolCalls.every((call) => {
265225
+ const result = results.get(call.id);
265226
+ return calls.get(call.id)?.count === 1 && result?.count === 1 && result.index > index && result.index < boundary;
265227
+ })) closedBatches.add(index);
265228
+ }
265229
+ const projected = [...history];
265230
+ let changed = false;
265231
+ for (let index = 0; index < boundary; index++) {
265232
+ context.signal.throwIfAborted();
265233
+ const message = history[index];
265234
+ if (message.role !== "tool" || !message.toolCallId) continue;
265235
+ const call = calls.get(message.toolCallId);
265236
+ if (call === void 0 || call.count !== 1 || !closedBatches.has(call.index) || !knownTools.has(call.name)) continue;
265237
+ const text = persistableText(message);
265238
+ if (text === void 0) continue;
265239
+ const digest = createHash("sha256").update(JSON.stringify([
265240
+ canonical(this.agent.homedir),
265241
+ call.name,
265242
+ message.toolCallId,
265243
+ text
265244
+ ])).digest("hex");
265245
+ const outputPath = join(resolve(this.agent.homedir), "tool-results", "historical", `${digest}.txt`);
265246
+ 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.
265247
+
265248
+ [preview]
265249
+ `;
265250
+ if (header.length >= MAX_REFERENCE_CHARS) continue;
265251
+ try {
265252
+ if (!await this.canRead(read, outputPath, context)) continue;
265253
+ const save = this.pending.then(() => this.persist(outputPath, text));
265254
+ this.pending = save.catch(() => void 0);
265255
+ await save;
265256
+ const stat = await this.agent.kaos.stat(outputPath);
265257
+ if ((stat.stMode & 61440) !== 32768 || stat.stSize !== Buffer.byteLength(text)) continue;
265258
+ const bytes = await this.agent.kaos.readBytes(outputPath, Buffer.byteLength(text) + 1);
265259
+ if (!Buffer.from(bytes).equals(Buffer.from(text)) || detectFileType(outputPath, Buffer.from(bytes).subarray(0, 512)).kind !== "text") continue;
265260
+ await this.verifyFile(outputPath, text);
265261
+ context.signal.throwIfAborted();
265262
+ if (!await this.canRead(read, outputPath, context)) continue;
265263
+ let preview = text.slice(0, MAX_REFERENCE_CHARS - header.length);
265264
+ if (/[\uD800-\uDBFF]$/.test(preview)) preview = preview.slice(0, -1);
265265
+ projected[index] = {
265266
+ ...message,
265267
+ content: [{
265268
+ type: "text",
265269
+ text: header + preview
265270
+ }]
265271
+ };
265272
+ changed = true;
265273
+ } catch {
265274
+ context.signal.throwIfAborted();
265275
+ }
265276
+ }
265277
+ try {
265278
+ for (const path of this.dirtyDirectories) await this.verifyDirectory(path);
265279
+ this.dirtyDirectories.clear();
265280
+ } catch {
265281
+ return unchanged;
265282
+ }
265283
+ return {
265284
+ history: changed ? projected : history,
265285
+ changed
265286
+ };
265287
+ }
265288
+ async canRead(read, path, context) {
265289
+ if (this.agent.tools.loopTools.find((tool) => tool.name === "Read") !== read || this.agent.hooks?.hasMatchingHook("PreToolUse", "Read")) return false;
265290
+ const args = {
265291
+ path,
265292
+ line_offset: 1,
265293
+ n_lines: 1e3
265294
+ };
265295
+ const execution = read.resolveExecution(args);
265296
+ if ("isError" in execution) return false;
265297
+ const toolCall = {
265298
+ type: "function",
265299
+ id: "historical-recovery-check",
265300
+ name: "Read",
265301
+ arguments: JSON.stringify(args)
265302
+ };
265303
+ for (const policy of this.agent.permission.policies) {
265304
+ const result = await policy.evaluate({
265305
+ ...context,
265306
+ tool: read,
265307
+ toolCall,
265308
+ toolCalls: [toolCall],
265309
+ args,
265310
+ execution
265311
+ });
265312
+ if (result !== void 0) return result.kind === "approve" && result.executionMetadata === void 0;
265313
+ }
265314
+ return false;
265315
+ }
265316
+ async ensureDirectory(path) {
265317
+ let created = false;
265318
+ try {
265319
+ await mkdir(path, { mode: 448 });
265320
+ created = true;
265321
+ } catch (error) {
265322
+ if (error.code !== "EEXIST") throw error;
265323
+ }
265324
+ if (created && this.directories.has(dirname(path))) this.dirtyDirectories.add(dirname(path));
265325
+ const stat = await checkedDirectory(path);
265326
+ const previous = this.directories.get(path);
265327
+ if (previous !== void 0 && previous.identity !== identity(stat)) throw new Error("Recovery directory replaced");
265328
+ if (previous === void 0) {
265329
+ if (created) await hardenPrivatePath(path, "directory");
265330
+ await this.verifyDirectory(path);
265331
+ } else if (previous.revision !== revision(stat) && !this.dirtyDirectories.has(path)) await this.verifyDirectory(path);
265332
+ }
265333
+ async verifyDirectory(path) {
265334
+ const before = await checkedDirectory(path);
265335
+ const previous = this.directories.get(path);
265336
+ if (previous !== void 0 && previous.identity !== identity(before)) throw new Error("Recovery directory replaced");
265337
+ if (process.platform === "win32") new WindowsPrivateAcl().verify(path, "directory");
265338
+ else if ((before.mode & 63n) !== 0n) throw new Error("Recovery directory is not private");
265339
+ const after = await checkedDirectory(path);
265340
+ if (revision(before) !== revision(after)) throw new Error("Recovery directory changed during verification");
265341
+ this.directories.set(path, {
265342
+ identity: identity(after),
265343
+ revision: revision(after)
265344
+ });
265345
+ }
265346
+ async persist(path, text) {
265347
+ await checkedAncestors(dirname(dirname(dirname(path))));
265348
+ await this.ensureDirectory(dirname(dirname(path)));
265349
+ await this.ensureDirectory(dirname(path));
265350
+ if (this.files.has(path)) {
265351
+ await this.verifyFile(path, text);
265352
+ return;
265353
+ }
265354
+ try {
265355
+ await lstat(path);
265356
+ await this.verifyFile(path, text);
265357
+ if (process.platform === "win32") new WindowsPrivateAcl().verify(path, "file");
265358
+ this.files.set(path, revision(await lstat(path, { bigint: true })));
265359
+ return;
265360
+ } catch (error) {
265361
+ if (error.code !== "ENOENT") throw error;
265362
+ }
265363
+ const staging = join(dirname(path), `${randomUUID()}.staging`);
265364
+ this.dirtyDirectories.add(dirname(path));
265365
+ try {
265366
+ await atomicWritePrivate(staging, text);
265367
+ await checkedAncestors(dirname(path));
265368
+ await link(staging, path);
265369
+ } finally {
265370
+ await unlink(staging).catch(() => void 0);
265371
+ }
265372
+ await this.verifyFile(path, text);
265373
+ this.files.set(path, revision(await lstat(path, { bigint: true })));
265374
+ }
265375
+ async verifyFile(path, text) {
265376
+ const before = await lstat(path, { bigint: true });
265377
+ 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");
265378
+ const previous = this.files.get(path);
265379
+ if (previous !== void 0 && previous !== revision(before)) throw new Error("Recovery artifact changed");
265380
+ const handle = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
265381
+ try {
265382
+ 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");
265383
+ } finally {
265384
+ await handle.close();
265385
+ }
265386
+ }
265387
+ };
265388
+ //#endregion
265389
+ //#region ../../packages/agent-core/src/agent/response-review-decision.ts
265390
+ const STAGES = [
265391
+ "setup",
265392
+ "native",
265393
+ "release",
265394
+ "verification"
265395
+ ];
265396
+ const REASONS = [
265397
+ "invalid_settings",
265398
+ "connection_unavailable",
265399
+ "connection_changed",
265400
+ "native_unavailable",
265401
+ "native_rejected",
265402
+ "release_denied",
265403
+ "release_rejected",
265404
+ "verification_denied",
265405
+ "verification_rejected",
265406
+ "binding_mismatch",
265407
+ "cancelled",
265408
+ "exception"
265409
+ ];
265410
+ const FINDINGS = new Set([
265411
+ "empty-target",
265412
+ "unicode-not-nfc",
265413
+ "replacement-character",
265414
+ "nul-character",
265415
+ "script-mismatch",
265416
+ "missing-language-character-profile",
265417
+ "ascii-folding-pressure",
265418
+ "short-text-native-review-required",
265419
+ "suspected-ascii-substitution",
265420
+ "invalid-target-type",
265421
+ "exact-language-required",
265422
+ "missing-response-attestations",
265423
+ "isolated-guard-unavailable",
265424
+ "unrecognized-finding"
265425
+ ]);
265426
+ /** Never carry provider/guard prose, signed receipts or arbitrary codes into logs. */
265427
+ function rejectionFindings(report) {
265428
+ const codes = [];
265429
+ if (report?.["reason"] !== void 0) codes.push(report["reason"]);
265430
+ if (Array.isArray(report?.["findings"])) for (const item of report["findings"].slice(0, 32)) codes.push(typeof item === "object" && item !== null && "code" in item ? item.code : void 0);
265431
+ const safe = [...new Set(codes.map((code) => typeof code === "string" && FINDINGS.has(code) ? code : "unrecognized-finding"))];
265432
+ return safe.length ? safe : void 0;
265433
+ }
265434
+ function rejectionMessage(value) {
265435
+ if (!STAGES.includes(value.stage) || !REASONS.includes(value.reason)) return void 0;
265436
+ const findings = rejectionFindings({ findings: value.findings?.map((code) => ({ code })) });
265437
+ return `Response review rejected candidate: ${value.stage}/${value.reason}` + (findings?.length ? ` (${findings.join(", ")})` : "");
265438
+ }
265439
+ //#endregion
265440
+ //#region ../../packages/agent-core/src/agent/response-review-deadline.ts
265441
+ const IDLE_TIMEOUT_MS = 3e4;
265442
+ const NATIVE_FIRST_CONTENT_MS = 9e4;
265443
+ const ABSOLUTE_TIMEOUT_MS = 12e4;
265444
+ function createResponseReviewDeadline(source) {
265445
+ const absoluteExpiresAt = performance.now() + ABSOLUTE_TIMEOUT_MS;
265446
+ const absolute = createDeadlineAbortSignal(source, ABSOLUTE_TIMEOUT_MS);
265447
+ const controller = new AbortController();
265448
+ const unlink = linkAbortSignal(absolute.signal, controller);
265449
+ let stage = "setup";
265450
+ let didTimeout = false;
265451
+ let closed = false;
265452
+ let idle;
265453
+ let idleExpiresAt = 0;
265454
+ const expire = () => {
265455
+ didTimeout = true;
265456
+ controller.abort(abortError());
265457
+ };
265458
+ const expireIfDue = () => {
265459
+ if (closed || controller.signal.aborted) return;
265460
+ const now = performance.now();
265461
+ if (now >= absoluteExpiresAt || now >= idleExpiresAt) expire();
265462
+ };
265463
+ const refresh = (timeoutMs) => {
265464
+ idleExpiresAt = performance.now() + timeoutMs;
265465
+ if (idle !== void 0) clearTimeout(idle);
265466
+ idle = setTimeout(expire, timeoutMs);
265467
+ };
265468
+ refresh(IDLE_TIMEOUT_MS);
265469
+ return {
265470
+ signal: controller.signal,
265471
+ timedOut: () => didTimeout || absolute.timedOut(),
265472
+ throwIfAborted: () => {
265473
+ expireIfDue();
265474
+ controller.signal.throwIfAborted();
265475
+ },
265476
+ progress: (event) => {
265477
+ expireIfDue();
265478
+ if (closed || controller.signal.aborted) return;
265479
+ if (event === "native.start" && stage === "setup") {
265480
+ stage = "native";
265481
+ refresh(NATIVE_FIRST_CONTENT_MS);
265482
+ } else if (event === "native.text" && stage === "native") refresh(IDLE_TIMEOUT_MS);
265483
+ else if (event === "release.start" && stage === "native") {
265484
+ stage = "release";
265485
+ refresh(IDLE_TIMEOUT_MS);
265486
+ } else if (event === "verification.start" && stage === "release") {
265487
+ stage = "verification";
265488
+ refresh(IDLE_TIMEOUT_MS);
265489
+ }
265490
+ },
265491
+ clear: () => {
265492
+ closed = true;
265493
+ if (idle !== void 0) clearTimeout(idle);
265494
+ idle = void 0;
265495
+ unlink();
265496
+ absolute.clear();
265497
+ }
265498
+ };
265499
+ }
265500
+ //#endregion
265501
+ //#region ../../packages/agent-core/src/agent/response-review.ts
265502
+ const MAX_CANDIDATE_BYTES = 1048576;
265503
+ const MAX_CANDIDATE_PARTS = 4096;
265504
+ const REVIEW_CANCEL_SETTLE_MS = 1e3;
265505
+ const MAX_REVIEW_ATTEMPTS = 2;
265506
+ function createReviewedOutputDispatcher(options) {
265507
+ let pending = [];
265508
+ let bytes = 0;
265509
+ let stepUuid;
265510
+ let epoch = 0;
265511
+ let reviewing = false;
265512
+ let turnId;
265513
+ let stepNumber = 0;
265514
+ let toolTextWithheld = false;
265515
+ const toolCalls = /* @__PURE__ */ new Map();
265516
+ const toolResults = /* @__PURE__ */ new Set();
265517
+ let invalidToolExchange = false;
265518
+ function clear() {
265519
+ pending = [];
265520
+ bytes = 0;
265521
+ stepUuid = void 0;
265522
+ epoch += 1;
265523
+ reviewing = false;
265524
+ toolCalls.clear();
265525
+ toolResults.clear();
265526
+ invalidToolExchange = false;
265527
+ }
265528
+ function dispatch(event) {
265529
+ if (event.type === "text.delta") {
265530
+ options.onTextProgress?.();
265531
+ return;
265532
+ }
265533
+ if (event.type === "step.begin") {
265534
+ clear();
265535
+ if (turnId !== event.turnId) toolTextWithheld = false;
265536
+ turnId = event.turnId;
265537
+ stepNumber = event.step;
265538
+ stepUuid = event.uuid;
265539
+ return options.dispatch(event);
265540
+ }
265541
+ if (event.type === "content.part" && event.part.type === "text") {
265542
+ if (reviewing) {
265543
+ clear();
265544
+ return Promise.reject(/* @__PURE__ */ new Error("Response review candidate changed during verification"));
265545
+ }
265546
+ if (stepUuid !== event.stepUuid) return Promise.reject(/* @__PURE__ */ new Error("Response review step mismatch"));
265547
+ bytes += Buffer.byteLength(event.part.text);
265548
+ if (bytes > MAX_CANDIDATE_BYTES || pending.length >= MAX_CANDIDATE_PARTS) {
265549
+ clear();
265550
+ return Promise.reject(/* @__PURE__ */ new Error("Response review candidate exceeds limit"));
265551
+ }
265552
+ pending.push({
265553
+ ...event,
265554
+ part: { ...event.part }
265555
+ });
265556
+ return Promise.resolve();
265557
+ }
265558
+ if (event.type === "step.end") return finish(event);
265559
+ if (event.type === "turn.interrupted") clear();
265560
+ if (event.type === "step.retrying") {
265561
+ clear();
265562
+ stepUuid = event.stepUuid;
265563
+ }
265564
+ if (event.type === "tool.call") {
265565
+ const recordedEpoch = epoch;
265566
+ const { toolCallId, uuid } = event;
265567
+ const matchesStep = event.stepUuid === stepUuid && event.turnId === turnId && event.step === stepNumber;
265568
+ return options.dispatch(event).then(() => {
265569
+ if (recordedEpoch !== epoch) return;
265570
+ if (!matchesStep || toolCalls.has(toolCallId)) invalidToolExchange = true;
265571
+ else toolCalls.set(toolCallId, uuid);
265572
+ });
265573
+ }
265574
+ if (event.type === "tool.result") {
265575
+ const recordedEpoch = epoch;
265576
+ const { toolCallId, parentUuid } = event;
265577
+ return options.dispatch(event).then(() => {
265578
+ if (recordedEpoch !== epoch) return;
265579
+ if (toolCalls.get(toolCallId) !== parentUuid || toolResults.has(toolCallId)) invalidToolExchange = true;
265580
+ else toolResults.add(toolCallId);
265581
+ });
265582
+ }
265583
+ if (event.type === "content.part") return options.dispatch(event);
265584
+ options.dispatch(event);
265585
+ }
265586
+ async function finish(event) {
265587
+ if (pending.length === 0) return options.dispatch(event);
265588
+ if (event.uuid !== stepUuid) throw new Error("Response review step mismatch");
265589
+ const currentEpoch = epoch;
265590
+ const isCurrentCandidate = () => epoch === currentEpoch;
265591
+ const parts = [...pending];
265592
+ reviewing = true;
265593
+ const text = parts.map((part) => part.part.type === "text" ? part.part.text : "").join("");
265594
+ try {
265595
+ if (event.finishReason !== "end_turn" && event.finishReason !== "tool_use") throw new Error("Response review requires a complete candidate");
265596
+ if (options.review === void 0) throw new Error("Response review unavailable");
265597
+ options.signal.throwIfAborted();
265598
+ let approval;
265599
+ let rejection;
265600
+ let nativeRejection = false;
265601
+ for (let attempt = 1; attempt <= MAX_REVIEW_ATTEMPTS; attempt += 1) {
265602
+ options.signal.throwIfAborted();
265603
+ if (epoch !== currentEpoch) throw new Error("Response review candidate changed during verification");
265604
+ const deadline = createResponseReviewDeadline(options.signal);
265605
+ let requested;
265606
+ const pendingOperations = [];
265607
+ let attemptRejection;
265608
+ let attemptNativeRejection = false;
265609
+ try {
265610
+ requested = options.review({
265611
+ text,
265612
+ turnId: event.turnId,
265613
+ step: event.step,
265614
+ signal: deadline.signal,
265615
+ onProgress: (progress) => {
265616
+ if (isCurrentCandidate()) deadline.progress(progress);
265617
+ },
265618
+ onPendingOperation: (operation) => {
265619
+ pendingOperations.push(operation.then(() => void 0, () => void 0));
265620
+ },
265621
+ onRejected: (decision) => {
265622
+ if (!deadline.signal.aborted) {
265623
+ const message = rejectionMessage(decision);
265624
+ attemptNativeRejection = attemptRejection === void 0 && message !== void 0 && decision.stage === "native" && decision.reason === "native_rejected";
265625
+ attemptRejection = message;
265626
+ }
265627
+ }
265628
+ });
265629
+ approval = await abortable(requested, deadline.signal);
265630
+ deadline.throwIfAborted();
265631
+ rejection = attemptRejection;
265632
+ nativeRejection = attemptNativeRejection;
265633
+ break;
265634
+ } catch {
265635
+ options.signal.throwIfAborted();
265636
+ if (attemptRejection !== void 0) throw new Error(attemptRejection);
265637
+ if (deadline.timedOut() && attempt < MAX_REVIEW_ATTEMPTS && requested !== void 0 && epoch === currentEpoch && await waitForCancelledReview(requested, pendingOperations)) continue;
265638
+ throw new Error(deadline.timedOut() ? "Response review timed out" : "Response review failed");
265639
+ } finally {
265640
+ deadline.clear();
265641
+ }
265642
+ }
265643
+ options.signal.throwIfAborted();
265644
+ if (epoch !== currentEpoch) throw new Error("Response review candidate changed during verification");
265645
+ if (nativeRejection && !toolTextWithheld && event.finishReason === "tool_use" && event.turnId === turnId && event.step === stepNumber && !invalidToolExchange && toolCalls.size > 0 && toolResults.size === toolCalls.size) {
265646
+ toolTextWithheld = true;
265647
+ await options.dispatch(event);
265648
+ try {
265649
+ options.onToolTextWithheld?.({
265650
+ turnId: event.turnId,
265651
+ step: event.step
265652
+ });
265653
+ } catch {}
265654
+ return;
265655
+ }
265656
+ if (rejection !== void 0) throw new Error(rejection);
265657
+ if (epoch !== currentEpoch || approval?.targetSha256 !== createHash("sha256").update(text).digest("hex") || !Number.isSafeInteger(approval.expiresAt) || approval.expiresAt <= Date.now()) throw new Error("Response review did not approve the exact current candidate");
265658
+ for (const part of parts) {
265659
+ options.signal.throwIfAborted();
265660
+ if (epoch !== currentEpoch || approval.expiresAt <= Date.now()) throw new Error("Response review approval no longer current");
265661
+ await options.dispatch(part);
265662
+ }
265663
+ options.signal.throwIfAborted();
265664
+ if (epoch !== currentEpoch || approval.expiresAt <= Date.now()) throw new Error("Response review approval no longer current");
265665
+ options.dispatch({
265666
+ type: "text.delta",
265667
+ delta: text
265668
+ });
265669
+ await options.dispatch(event);
265670
+ } finally {
265671
+ if (epoch === currentEpoch) clear();
265672
+ }
265673
+ }
265674
+ async function waitForCancelledReview(requested, pendingOperations) {
265675
+ const deadline = createDeadlineAbortSignal(options.signal, REVIEW_CANCEL_SETTLE_MS);
265676
+ try {
265677
+ await abortable(requested.then(() => void 0, () => void 0), deadline.signal);
265678
+ let observed = 0;
265679
+ while (observed < pendingOperations.length) {
265680
+ const batch = pendingOperations.slice(observed);
265681
+ observed = pendingOperations.length;
265682
+ await abortable(Promise.all(batch), deadline.signal);
265683
+ }
265684
+ options.signal.throwIfAborted();
265685
+ return true;
265686
+ } catch {
265687
+ options.signal.throwIfAborted();
265688
+ return false;
265689
+ } finally {
265690
+ deadline.clear();
265691
+ }
265692
+ }
265693
+ return dispatch;
265694
+ }
265695
+ //#endregion
265696
+ //#region ../../packages/agent-core/src/agent/turn/index.ts
265697
+ function blunExtractText(content) {
265698
+ if (typeof content === "string") return content;
265699
+ if (Array.isArray(content)) return content.map((c) => typeof c === "string" ? c : c && typeof c === "object" && "text" in c && typeof c.text === "string" ? c.text : "").join(" ");
265700
+ return "";
265701
+ }
265702
+ /**
265703
+ * BLUN: decide whether a turn should carry the full tool set. Small-context models
265704
+ * (e.g. King on an 8k window) lose their identity/language when all tool schemas
265705
+ * flood the prompt. A plain greeting omits task tools but keeps any enabled
265706
+ * language review workflow. Conservative: only very short, clearly
265707
+ * conversational messages skip task tools; anything task-like keeps the full set.
265708
+ */
265709
+ function blunTurnNeedsTools(input, origin) {
265710
+ if (origin.kind !== "user") return true;
265711
+ const text = blunExtractText(input).trim().toLowerCase();
265712
+ if (text.length === 0 || text.length > 48) return true;
265713
+ return !/^(?:hi|hallo|hey|hello|hoi|moin|servus|yo|danke|thanks|thx|bye|tschau|ciao)[.!?]*$/.test(text) && !/^(?:wer bist du|who are you|wie geht(?: es dir)?|how are you|na)[.!?]*$/.test(text);
265714
+ }
265715
+ function blunThinkingIntentInput(input) {
265716
+ const channelText = [...blunExtractText(input).matchAll(/(?:^|\r?\n)<channel\b[^>]*>\r?\n([\s\S]*?)\r?\n<\/channel>(?=\r?\n|$)/gi)].at(-1)?.[1];
265717
+ return channelText === void 0 ? input : [{
265718
+ type: "text",
265719
+ text: channelText
265720
+ }];
265721
+ }
265722
+ function blunTurnHasAttachment(input) {
265723
+ return input.some((part) => part.type !== "text") || /\b(?:image_path|attachment_file_id)\s*=/i.test(blunExtractText(input));
265724
+ }
265725
+ function greetingHistory(history, anchor) {
265726
+ const start = anchor === void 0 ? -1 : history.indexOf(anchor);
265727
+ if (start < 0) return history;
265728
+ const recent = [];
265729
+ let chars = 0;
265730
+ for (let i = start - 1; i >= 0 && recent.length < 6; i -= 1) {
265731
+ const message = history[i];
265732
+ if (message.role === "tool" || message.toolCalls.length > 0) break;
265733
+ if (message.role !== "assistant" && message.origin?.kind !== "user") continue;
265734
+ const size = blunExtractText(message.content).length;
265735
+ if (chars + size > 12e3 || message.content.some((part) => part.type !== "text")) break;
265736
+ recent.unshift(message);
265737
+ chars += size;
265738
+ }
265739
+ while (recent.length > 0 && recent[0].role !== "user") recent.shift();
265740
+ return [...recent, ...history.slice(start)];
265741
+ }
265742
+ /**
265743
+ * BLUN: builtin tools kept when the full schema set would overflow a small
265744
+ * context window. The essential read / edit / run / track loop — enough for
265745
+ * King to do real work on a 32k window without the full ~15k-token tool floor
265746
+ * that pushes a single task turn to ~82% context and into a compaction
265747
+ * death-loop (compaction can only shrink conversation, never the fixed tool
265748
+ * floor, so it re-triggers every turn).
265749
+ */
265750
+ const BLUN_LEAN_TOOL_NAMES = new Set([
265751
+ "Read",
265752
+ "ReadBatch",
265753
+ "Write",
265754
+ "Edit",
265755
+ "Grep",
265756
+ "Glob",
265757
+ "Bash",
265758
+ "TodoList",
265759
+ "Skill",
265760
+ "CompactConversation",
265761
+ "MistakeRecord"
265762
+ ]);
265763
+ const BLUN_LANGUAGE_REVIEW_TOOL_NAMES = new Set([
265764
+ "release_response",
265765
+ "release_translation",
265766
+ "verify_release_token",
265767
+ "validate_text"
265768
+ ].map((name) => qualifyMcpToolName("blun-language-guard", name)));
265769
+ /**
265770
+ * Channel/session MCP tools that must survive the lean cut — e.g. the Telegram
265771
+ * reply tools. Without these a bridged session can RECEIVE a message but has no
265772
+ * way to answer it (King processes the turn, then has no reply tool). Their
265773
+ * schemas are tiny, so keeping them does not reappear the compaction floor.
265774
+ * Matches both bare names and MCP-prefixed variants (…__reply).
265775
+ *
265776
+ */
265777
+ const BLUN_LEAN_KEEP_RE = /(^|__|:)(reply|react|edit_message|download_attachment|memory_(?:status|settings_update|remember|list|threads_list|recall))$/i;
265778
+ /** Telegram writes require a real inbound user turn, never an internal nudge. */
265779
+ const BLUN_TELEGRAM_OUTBOUND_TOOL_RE = /^mcp__[^\s]*telegram[^\s]*__(?:reply|react|edit_message)$/i;
265780
+ function blunToolsForOrigin(tools, origin) {
265781
+ if (origin.kind !== "system_trigger" && origin.kind !== "injection") return tools;
265782
+ return tools.filter((tool) => !BLUN_TELEGRAM_OUTBOUND_TOOL_RE.test(tool.name));
265783
+ }
265784
+ /**
265785
+ * Restore bounded schema disclosure even on large-context models. Only tools
265786
+ * already eligible for this turn enter the loader; execution still follows
265787
+ * the ordinary permission pipeline.
265788
+ */
265789
+ function blunSelectTurnTools(tools, maxContextTokens, loadedToolNames = /* @__PURE__ */ new Set(), requiredToolNames = /* @__PURE__ */ new Set(), selected = []) {
265790
+ if (estimateTokensForTools(tools) <= (0, import_turn_tool_performance_policy.toolSchemaBudgetTokens)(maxContextTokens) || tools.some((tool) => tool.name === "ToolSearch")) {
265791
+ selected.splice(0, selected.length, ...tools);
265792
+ return selected;
265793
+ }
265794
+ selected.splice(0, selected.length, ...tools.filter((tool) => BLUN_LEAN_TOOL_NAMES.has(tool.name) || BLUN_LEAN_KEEP_RE.test(tool.name) || BLUN_LANGUAGE_REVIEW_TOOL_NAMES.has(tool.name) || loadedToolNames.has(tool.name) || requiredToolNames.has(tool.name)));
265795
+ const names = new Set(selected.map((tool) => tool.name));
265796
+ const deferred = tools.filter((tool) => !names.has(tool.name));
265797
+ if (deferred.length > 0) selected.push((0, import_turn_tool_performance_policy.createDeferredToolLoader)(selected, deferred, loadedToolNames));
265798
+ return selected;
265799
+ }
265800
+ const LLM_NOT_SET_MESSAGE = "LLM not set, send \"/login\" to login";
265801
+ /** Origin tag for the synthetic "continue" prompt that drives each goal turn. */
265802
+ const GOAL_CONTINUATION_ORIGIN = {
265803
+ kind: "system_trigger",
265804
+ name: "goal_continuation"
265805
+ };
265806
+ function goalContinuationOrigin(origin) {
265807
+ const cronRunId = origin.kind === "cron_job" ? origin.runId : origin.kind === "system_trigger" && origin.name === "goal_continuation" ? origin.cronRunId : void 0;
265808
+ return cronRunId === void 0 ? GOAL_CONTINUATION_ORIGIN : {
265809
+ kind: "system_trigger",
265810
+ name: "goal_continuation",
265811
+ cronRunId
265812
+ };
265813
+ }
265814
+ const GOAL_COMPLETION_REMINDER_NAME = "goal_completion";
265815
+ const GOAL_BLOCKED_REMINDER_NAME = "goal_blocked";
265816
+ const GOAL_RATE_LIMIT_PAUSE_REASON = "Paused after provider rate limit";
265817
+ const GOAL_PROVIDER_CONNECTION_PAUSE_PREFIX = "Paused after provider connection error";
265818
+ const GOAL_PROVIDER_AUTH_PAUSE_PREFIX = "Paused after provider authentication error";
265819
+ const GOAL_PROVIDER_API_PAUSE_PREFIX = "Paused after provider API error";
265820
+ const GOAL_MODEL_CONFIG_PAUSE_PREFIX = "Paused after model configuration error";
265821
+ const GOAL_RUNTIME_PAUSE_PREFIX = "Paused after runtime error";
265822
+ const GOAL_PROVIDER_FILTERED_PAUSE_REASON = "Paused after provider safety policy block";
265823
+ /**
265824
+ * The prompt the goal driver appends to start each continuation turn — the
265825
+ * autonomous stand-in for the user typing "continue". The model decides when to
265826
+ * stop by calling `UpdateGoal`; otherwise the driver runs another turn.
265827
+ */
265828
+ const GOAL_CONTINUATION_PROMPT = [
265829
+ "Continue working toward the active goal.",
265830
+ "Keep the self-audit brief. Do not explore unrelated interpretations once the goal can be",
265831
+ "decided. If the objective is simple, already answered, impossible, unsafe, or contradictory,",
265832
+ "do not run another goal turn. Explain briefly if useful, then call UpdateGoal with `complete`",
265833
+ "or `blocked` in the same turn. Otherwise, weigh the objective and any completion criteria",
265834
+ "against the work done so far. Goal mode is iterative: do one coherent slice of work, then",
265835
+ "reassess.",
265836
+ "",
265837
+ "CRITICAL — WHEN TO END THE GOAL:",
265838
+ "When all required work is done — files written, code executed, tests passing, validation",
265839
+ "passed — you MUST call UpdateGoal with `complete` IMMEDIATELY. Do not narrerate the next step",
265840
+ "and stop without calling it. Do not leave the goal active when the work is finished.",
265841
+ "If you have completed the task and are about to end your response, call UpdateGoal with",
265842
+ "`complete` FIRST, then give your summary. Never end a turn with the goal still active if",
265843
+ "the work is actually done.",
265844
+ "",
265845
+ "Do not mark complete after only producing a plan, summary, first pass, or partial result.",
265846
+ "Do not mark complete after announcing the next step without executing it.",
265847
+ "If an external condition or required user input prevents progress, or the objective cannot",
265848
+ "be completed as stated, call UpdateGoal with `blocked`. On error or failure, call",
265849
+ "UpdateGoal with `blocked`, NOT `complete`.",
265850
+ "Otherwise keep going — use the existing conversation context and your tools,",
265851
+ "and do not ask the user for input unless a real blocker prevents progress."
265852
+ ].join(" ");
265853
+ var TurnFlow = class {
265854
+ agent;
265855
+ admissionClosed = false;
265856
+ loadedToolNames = /* @__PURE__ */ new Set();
265857
+ refreshSelectedTools;
265858
+ executions = new ExecutionLedger();
265859
+ executingControllers = /* @__PURE__ */ new Set();
265860
+ steerBuffer = [];
265861
+ nextSteerSequence = 0;
265862
+ turnId = -1;
265863
+ activeTurn = null;
265864
+ toolCallStartedAt = /* @__PURE__ */ new Map();
265865
+ toolCallDupType = /* @__PURE__ */ new Map();
265866
+ stepToolCallKeys = /* @__PURE__ */ new Map();
265867
+ telemetryModeByTurn = /* @__PURE__ */ new Map();
265868
+ currentStepByTurn = /* @__PURE__ */ new Map();
265869
+ interruptedTelemetryTurnIds = /* @__PURE__ */ new Set();
265870
+ stepFailureByTurn = /* @__PURE__ */ new Map();
265871
+ providerRequestFailureByTurn = /* @__PURE__ */ new Map();
265872
+ currentStep = 0;
265873
+ historicalToolProjection;
265874
+ constructor(agent) {
265875
+ this.agent = agent;
265876
+ this.historicalToolProjection = new HistoricalToolProjection(agent);
265877
+ }
265878
+ /** Explicit host shutdown only; ordinary cancellation remains resumable. */
265879
+ closeAdmission() {
265880
+ this.admissionClosed = true;
265881
+ }
265882
+ async quiesce(options) {
265883
+ const deadline = new QuiescenceDeadline(options.deadlineAt);
265884
+ this.closeAdmission();
265885
+ const reason = /* @__PURE__ */ new Error("Turn admission is closed for quiescence");
265886
+ if (this.hasActiveTurn) this.cancel(void 0, reason);
265887
+ for (const controller of this.executingControllers) controller.abort(reason);
265888
+ await deadline.wait(this.executions.drain());
265889
+ const checkedAt = Date.now();
265890
+ const remaining = this.executions.pending;
265891
+ return {
265892
+ admissionClosed: true,
265893
+ checkedAt,
265894
+ remaining,
265895
+ status: deadline.expired || remaining.length > 0 ? "incomplete" : "quiescent",
265896
+ reason: deadline.expired ? "deadline" : void 0
265897
+ };
265898
+ }
265899
+ /** Actual execution, unlike waitForCurrentTurn(signal), is not abortable. */
265900
+ waitForExecutionEnd() {
265901
+ return this.executions.drain();
265902
+ }
265903
+ get pendingExecutions() {
265904
+ return this.executions.pending;
265905
+ }
265906
+ assertAdmissionOpen() {
265907
+ if (this.admissionClosed) throw new Error("Turn admission is closed for quiescence");
265908
+ }
265909
+ /** Best-effort agent id (main / generated id) derived from the agent homedir. */
264452
265910
  get agentId() {
264453
265911
  return this.agent.homedir ? basename$1(this.agent.homedir) : this.agent.type;
264454
265912
  }
@@ -264821,7 +266279,10 @@ var TurnFlow = class {
264821
266279
  turnId,
264822
266280
  origin
264823
266281
  });
266282
+ const previousLastMessage = this.agent.context.history.at(-1);
264824
266283
  this.agent.context.appendUserMessage(input, origin);
266284
+ const appendedMessage = this.agent.context.history.at(-1);
266285
+ const turnAnchor = appendedMessage !== previousLastMessage && appendedMessage?.role === "user" && appendedMessage.origin === origin ? appendedMessage : void 0;
264825
266286
  const startedAt = Date.now();
264826
266287
  let ended;
264827
266288
  let blockedByUserPromptHook = false;
@@ -264848,7 +266309,7 @@ var TurnFlow = class {
264848
266309
  this.agent.log.warn("Turn-start observation failed");
264849
266310
  }
264850
266311
  signal.throwIfAborted();
264851
- const stopReason = await this.runStepLoop(turnId, signal, input, origin, (response) => {
266312
+ const stopReason = await this.runStepLoop(turnId, signal, input, origin, turnAnchor, (response) => {
264852
266313
  completedResponse = response;
264853
266314
  });
264854
266315
  completedStopReason = stopReason;
@@ -265013,7 +266474,7 @@ var TurnFlow = class {
265013
266474
  content: hookResult.message
265014
266475
  });
265015
266476
  }
265016
- async runStepLoop(turnId, signal, input, origin, onFinalResponse) {
266477
+ async runStepLoop(turnId, signal, input, origin, turnAnchor, onFinalResponse) {
265017
266478
  let stopHookContinuationUsed = false;
265018
266479
  let goalOutcomeMessageContinuationUsed = false;
265019
266480
  const deduper = new ToolCallDeduplicator({ telemetry: this.agent.telemetry });
@@ -265026,11 +266487,25 @@ var TurnFlow = class {
265026
266487
  let taskToolsNeeded = turnNeedsThinking || blunTurnHasAttachment(input);
265027
266488
  const greetingAnchor = this.agent.context.history.findLast((message) => message.role === "user" && message.origin?.kind === "user");
265028
266489
  let partialContext = false;
265029
- const turnHistory = () => {
265030
- const history = personalMemoryRecall === void 0 ? this.agent.context.history : [...this.agent.context.history, personalMemoryRecall];
266490
+ const turnHistory = async (tools) => {
266491
+ const raw = [...this.agent.context.history];
266492
+ const history = personalMemoryRecall === void 0 ? raw : [...raw, personalMemoryRecall];
265031
266493
  const selected = taskToolsNeeded ? history : greetingHistory(history, greetingAnchor);
265032
- partialContext = selected.length < history.length;
265033
- return selected;
266494
+ const projected = await this.historicalToolProjection.project(selected, turnAnchor, tools, {
266495
+ turnId: String(turnId),
266496
+ stepNumber: this.currentStep,
266497
+ signal,
266498
+ llm: turnLLM
266499
+ });
266500
+ const current = this.agent.context.history;
266501
+ if (current.length !== raw.length || current.some((message, index) => message !== raw[index])) {
266502
+ const latest = personalMemoryRecall === void 0 ? current : [...current, personalMemoryRecall];
266503
+ const retained = taskToolsNeeded ? latest : greetingHistory(latest, greetingAnchor);
266504
+ partialContext = retained.length < latest.length;
266505
+ return retained;
266506
+ }
266507
+ partialContext = selected.length < history.length || projected.changed;
266508
+ return projected.history;
265034
266509
  };
265035
266510
  const turnLLM = this.agent.llmForTurn(turnNeedsThinking ? void 0 : "off");
265036
266511
  const turnTools = this.agent.tools.captureTurnTools();
@@ -265066,8 +266541,8 @@ var TurnFlow = class {
265066
266541
  }, completion);
265067
266542
  },
265068
266543
  llm: turnLLM,
265069
- buildMessages: () => this.agent.context.project(turnHistory(), { dropOrphanResults: true }),
265070
- buildMessagesStrict: () => this.agent.context.project(turnHistory(), {
266544
+ buildMessages: async () => this.agent.context.project(await turnHistory(selectedTools), { dropOrphanResults: true }),
266545
+ buildMessagesStrict: async () => this.agent.context.project(await turnHistory(selectedTools), {
265071
266546
  synthesizeMissing: true,
265072
266547
  dropOrphanResults: true,
265073
266548
  dedupeDuplicateToolCalls: true,
@@ -268209,12 +269684,14 @@ function readPayload(result, includeError = false) {
268209
269684
  /** Receipts never enter ordinary tool execution, hooks, replay or model history. */
268210
269685
  function createGuardResponseReviewer(host) {
268211
269686
  const { language, guardVersion } = host;
268212
- return async ({ text, turnId, step, signal, onRejected }) => {
269687
+ return async ({ text, turnId, step, signal, onRejected, onPendingOperation, onProgress }) => {
268213
269688
  const input = {
268214
269689
  text,
268215
269690
  turnId,
268216
269691
  step,
268217
- signal
269692
+ signal,
269693
+ onPendingOperation,
269694
+ onProgress
268218
269695
  };
268219
269696
  const targetSha256 = digest(text);
268220
269697
  let stage = "setup";
@@ -268241,6 +269718,10 @@ function createGuardResponseReviewer(host) {
268241
269718
  const blocked = (reason, findings) => {
268242
269719
  record(signal.aborted ? "cancelled" : reason, findings);
268243
269720
  };
269721
+ const track = (operation) => {
269722
+ onPendingOperation?.(operation.then(() => void 0, () => void 0));
269723
+ return operation;
269724
+ };
268244
269725
  try {
268245
269726
  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");
268246
269727
  const resolve = () => {
@@ -268253,13 +269734,14 @@ function createGuardResponseReviewer(host) {
268253
269734
  const current = () => !signal.aborted && resolve()?.client === connection.client;
268254
269735
  if (!current()) return blocked("connection_changed");
268255
269736
  stage = "native";
268256
- const native = await abortable(host.reviewNative({ ...input }), signal);
269737
+ onProgress?.("native.start");
269738
+ const native = await abortable(track(host.reviewNative({ ...input })), signal);
268257
269739
  if (!current()) return blocked("connection_changed");
268258
269740
  if (native === void 0) return blocked("native_unavailable");
268259
269741
  if (!passed(native.nativeness) || !passed(native.orthography)) return blocked("native_rejected");
268260
269742
  const authorized = async (name) => {
268261
269743
  if (!current()) return false;
268262
- return passed(await abortable(host.authorize({
269744
+ return passed(await abortable(track(host.authorize({
268263
269745
  toolName: qualifyMcpToolName(SERVER_NAME, name),
268264
269746
  targetSha256,
268265
269747
  language,
@@ -268268,11 +269750,12 @@ function createGuardResponseReviewer(host) {
268268
269750
  turnId,
268269
269751
  step,
268270
269752
  signal
268271
- }), signal)) && current();
269753
+ })), signal)) && current();
268272
269754
  };
268273
269755
  stage = "release";
269756
+ onProgress?.("release.start");
268274
269757
  if (!await authorized("release_response")) return blocked("release_denied");
268275
- const releaseResult = await abortable(connection.client.callTool("release_response", {
269758
+ const releaseResult = await abortable(track(connection.client.callTool("release_response", {
268276
269759
  target_text: text,
268277
269760
  language,
268278
269761
  content_type: "prose",
@@ -268281,7 +269764,7 @@ function createGuardResponseReviewer(host) {
268281
269764
  nativeness: true,
268282
269765
  orthography: true
268283
269766
  }
268284
- }, signal), signal);
269767
+ }, signal)), signal);
268285
269768
  if (!current()) return blocked("connection_changed");
268286
269769
  if (releaseResult.isError) return blocked("release_rejected", rejectionFindings(readPayload(releaseResult, true)));
268287
269770
  const released = readPayload(releaseResult);
@@ -268289,8 +269772,9 @@ function createGuardResponseReviewer(host) {
268289
269772
  if (!current()) return blocked("connection_changed");
268290
269773
  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));
268291
269774
  stage = "verification";
269775
+ onProgress?.("verification.start");
268292
269776
  if (!await authorized("verify_release_token")) return blocked("verification_denied");
268293
- const { status: verificationStatus, valid, checks, payload } = readPayload(await abortable(connection.client.callTool("verify_release_token", {
269777
+ const { status: verificationStatus, valid, checks, payload } = readPayload(await abortable(track(connection.client.callTool("verify_release_token", {
268294
269778
  release_token,
268295
269779
  source_text: "",
268296
269780
  target_text: text,
@@ -268298,7 +269782,7 @@ function createGuardResponseReviewer(host) {
268298
269782
  purpose: "response",
268299
269783
  content_type: "prose",
268300
269784
  short_text_reviewed: false
268301
- }, signal), signal)) ?? {};
269785
+ }, signal)), signal)) ?? {};
268302
269786
  if (!current()) return blocked("connection_changed");
268303
269787
  if (verificationStatus !== "PASS" || valid !== true || !isPlainRecord(checks) || !CHECK_NAMES.every((name) => checks[name] === true) || !isPlainRecord(payload)) return blocked("verification_rejected");
268304
269788
  const { v, source_sha256, target_sha256, language: verifiedLanguage, purpose, content_type, short_text_reviewed, exp, iat } = payload;
@@ -268370,6 +269854,7 @@ async function reviewNativeResponse(agent, language, input) {
268370
269854
  });
268371
269855
  };
268372
269856
  const valueType = (value) => value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
269857
+ let formatRetry = false;
268373
269858
  for (let attempt = 1; attempt <= 2; attempt += 1) {
268374
269859
  let receivedBytes = 0;
268375
269860
  let outputLimitExceeded = false;
@@ -268378,21 +269863,27 @@ async function reviewNativeResponse(agent, language, input) {
268378
269863
  };
268379
269864
  const options = {
268380
269865
  signal,
269866
+ onExecution: (completion) => {
269867
+ input.onPendingOperation?.(completion.then(() => void 0, () => void 0));
269868
+ },
268381
269869
  requestLogFields: {
268382
269870
  turnStep: `review:${input.turnId}.${String(input.step)}`,
268383
269871
  attempt: `${attempt}/2`
268384
269872
  }
268385
269873
  };
268386
- 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.`;
269874
+ 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.`;
268387
269875
  try {
268388
269876
  signal.throwIfAborted();
268389
- const response = await abortable(agent.generate(provider, prompt, [], [createUserMessage(candidateMessage)], { onMessagePart: (part) => {
269877
+ const requested = agent.generate(provider, prompt, [], [createUserMessage(candidateMessage)], { onMessagePart: (part) => {
268390
269878
  if (part.type === "text") receivedBytes += Buffer.byteLength(part.text);
268391
269879
  if (receivedBytes > MAX_REVIEW_RESULT_BYTES) {
268392
269880
  outputLimitExceeded = true;
268393
269881
  throw new Error("Native review output exceeds limit");
268394
269882
  }
268395
- } }, options), signal);
269883
+ if (!signal.aborted && part.type === "text" && part.text.trim().length > 0) input.onProgress?.("native.text");
269884
+ } }, options);
269885
+ input.onPendingOperation?.(requested.then(() => void 0, () => void 0));
269886
+ const response = await abortable(requested, signal);
268396
269887
  if (response.usage !== null) agent.usage.record(modelAlias, response.usage, "turn");
268397
269888
  signal.throwIfAborted();
268398
269889
  if (response.message.toolCalls.length > 0) return invalid("tool_calls");
@@ -268405,7 +269896,10 @@ async function reviewNativeResponse(agent, language, input) {
268405
269896
  parsed = JSON.parse(text);
268406
269897
  } catch {
268407
269898
  record("invalid_json", attempt);
268408
- if (attempt === 1) continue;
269899
+ if (attempt === 1) {
269900
+ formatRetry = true;
269901
+ continue;
269902
+ }
268409
269903
  return;
268410
269904
  }
268411
269905
  if (!isPlainRecord(parsed) || Object.keys(parsed).length !== 2 || typeof parsed["nativeness"] !== "boolean" || typeof parsed["orthography"] !== "boolean") {
@@ -268416,7 +269910,10 @@ async function reviewNativeResponse(agent, language, input) {
268416
269910
  nativenessType: fields ? valueType(fields["nativeness"]) : "missing",
268417
269911
  orthographyType: fields ? valueType(fields["orthography"]) : "missing"
268418
269912
  });
268419
- if (attempt === 1 && !(fields && (fields["nativeness"] === false || fields["orthography"] === false))) continue;
269913
+ if (attempt === 1 && !(fields && (fields["nativeness"] === false || fields["orthography"] === false))) {
269914
+ formatRetry = true;
269915
+ continue;
269916
+ }
268420
269917
  return;
268421
269918
  }
268422
269919
  record(parsed["nativeness"] && parsed["orthography"] ? "approved" : "native_rejected", attempt, {
@@ -268427,7 +269924,12 @@ async function reviewNativeResponse(agent, language, input) {
268427
269924
  nativeness: parsed["nativeness"],
268428
269925
  orthography: parsed["orthography"]
268429
269926
  };
268430
- } catch {
269927
+ } catch (error) {
269928
+ if (!signal.aborted && !outputLimitExceeded && attempt === 1 && error instanceof APIProviderRateLimitError) {
269929
+ record("rate_limited", attempt);
269930
+ if (error.retryAfterMs === null) await agent.providerRetryWait.defer(providerRetryScope(modelAlias, provider), 1e3);
269931
+ continue;
269932
+ }
268431
269933
  return invalid(signal.aborted ? "cancelled" : outputLimitExceeded ? "output_limit" : "generation_failed");
268432
269934
  }
268433
269935
  }
@@ -309746,7 +311248,7 @@ const NINE = "9".charCodeAt(0);
309746
311248
  * @returns An array containing the integer step size and the integer offset of the nth rule.
309747
311249
  * @example nthCheck.parse("2n+3"); // returns [2, 3]
309748
311250
  */
309749
- function parse$5(formula) {
311251
+ function parse$6(formula) {
309750
311252
  formula = formula.trim().toLowerCase();
309751
311253
  if (formula === "even") return [2, 0];
309752
311254
  else if (formula === "odd") return [2, 1];
@@ -309846,7 +311348,7 @@ function compile$2(parsed) {
309846
311348
  * check(6); // `true`
309847
311349
  */
309848
311350
  function nthCheck(formula) {
309849
- return compile$2(parse$5(formula));
311351
+ return compile$2(parse$6(formula));
309850
311352
  }
309851
311353
  //#endregion
309852
311354
  //#region ../../node_modules/.pnpm/css-select@5.2.2/node_modules/css-select/lib/esm/pseudo-selectors/filters.js
@@ -315506,7 +317008,7 @@ var DOMParser = class DOMParser {
315506
317008
  };
315507
317009
  //#endregion
315508
317010
  //#region ../../node_modules/.pnpm/linkedom@0.18.12/node_modules/linkedom/esm/shared/parse-json.js
315509
- const { parse: parse$1 } = JSON;
317011
+ const { parse: parse$2 } = JSON;
315510
317012
  //#endregion
315511
317013
  //#region ../../node_modules/.pnpm/linkedom@0.18.12/node_modules/linkedom/esm/index.js
315512
317014
  const parseHTML$1 = (html, globals = null) => new DOMParser().parseFromString(html, "text/html", globals).defaultView;
@@ -331936,1142 +333438,6 @@ async function assertSuccess(response, operation) {
331936
333438
  throw new Error(`${operation} failed: HTTP ${String(response.status)}${detail ? `: ${detail}` : ""}`);
331937
333439
  }
331938
333440
  //#endregion
331939
- //#region ../../packages/kaos/src/errors.ts
331940
- /**
331941
- * Base error class for the kaos package.
331942
- */
331943
- var KaosError = class extends Error {
331944
- constructor(message) {
331945
- super(message);
331946
- this.name = "KaosError";
331947
- }
331948
- };
331949
- /**
331950
- * Equivalent to Python's FileExistsError — indicates a file or directory already exists.
331951
- */
331952
- var KaosFileExistsError = class extends KaosError {
331953
- constructor(message) {
331954
- super(message);
331955
- this.name = "KaosFileExistsError";
331956
- }
331957
- };
331958
- /**
331959
- * Thrown by `detectEnvironment` on Windows when no Git Bash install can be
331960
- * located. Carries the list of paths that were probed so callers can include
331961
- * them in install hints.
331962
- */
331963
- var KaosShellNotFoundError = class extends KaosError {
331964
- constructor(message) {
331965
- super(message);
331966
- this.name = "KaosShellNotFoundError";
331967
- }
331968
- };
331969
- //#endregion
331970
- //#region ../../packages/kaos/src/environment.ts
331971
- /**
331972
- * Environment — cross-platform probe of OS / shell.
331973
- *
331974
- * Detection is a pure function of injected probes (`platform` / `arch` /
331975
- * `release` / `env` / `isFile` / `execFileText`) so the same suite runs
331976
- * identically on any host OS. `detectEnvironmentFromNode()` bundles the Node
331977
- * defaults for production callers.
331978
- *
331979
- * On Windows the probe expects Git Bash (the canonical POSIX shell that
331980
- * ships with Git for Windows). If it cannot be located the function
331981
- * throws `KaosShellNotFoundError`; the SDK layer can wrap that into a
331982
- * user-facing install hint. Set `BLUN_SHELL_PATH` to override.
331983
- */
331984
- const GIT_EXEC_PATH_TIMEOUT_MS = 5e3;
331985
- function resolveOsKind(platform) {
331986
- switch (platform) {
331987
- case "darwin": return "macOS";
331988
- case "linux": return "Linux";
331989
- case "win32": return "Windows";
331990
- default: return platform;
331991
- }
331992
- }
331993
- async function detectEnvironment(deps) {
331994
- const osKind = resolveOsKind(deps.platform);
331995
- const osArch = deps.arch;
331996
- const osVersion = deps.release;
331997
- if (deps.platform === "win32") return {
331998
- osKind,
331999
- osArch,
332000
- osVersion,
332001
- shellName: "bash",
332002
- shellPath: await locateWindowsGitBash(deps)
332003
- };
332004
- const candidates = [
332005
- "/bin/bash",
332006
- "/usr/bin/bash",
332007
- "/usr/local/bin/bash"
332008
- ];
332009
- let found;
332010
- for (const p of candidates) if (await deps.isFile(p)) {
332011
- found = p;
332012
- break;
332013
- }
332014
- if (found !== void 0) return {
332015
- osKind,
332016
- osArch,
332017
- osVersion,
332018
- shellName: "bash",
332019
- shellPath: found
332020
- };
332021
- return {
332022
- osKind,
332023
- osArch,
332024
- osVersion,
332025
- shellName: "sh",
332026
- shellPath: "/bin/sh"
332027
- };
332028
- }
332029
- async function locateWindowsGitBash(deps) {
332030
- const checked = [];
332031
- const override = deps.env["BLUN_SHELL_PATH"]?.trim();
332032
- if (override !== void 0 && override.length > 0) {
332033
- checked.push(override);
332034
- if (await deps.isFile(override)) return override;
332035
- }
332036
- const gitExecutables = await findExecutablesOnPath("git.exe", deps.env["PATH"], deps.platform, deps.isFile);
332037
- for (const gitExe of gitExecutables) {
332038
- const inferred = gitBashCandidatesFromGitExe(gitExe);
332039
- if (inferred !== void 0) for (const candidate of inferred) {
332040
- checked.push(candidate);
332041
- if (await deps.isFile(candidate)) return candidate;
332042
- }
332043
- const gitExecPath = await readGitExecPath(deps, gitExe);
332044
- if (gitExecPath === void 0) continue;
332045
- for (const candidate of gitBashCandidatesFromGitExecPath(gitExecPath)) {
332046
- checked.push(candidate);
332047
- if (await deps.isFile(candidate)) return candidate;
332048
- }
332049
- }
332050
- const candidates = [
332051
- "C:\\Program Files\\Git\\bin\\bash.exe",
332052
- "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
332053
- "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
332054
- "C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
332055
- ];
332056
- const localAppData = deps.env["LOCALAPPDATA"]?.trim();
332057
- if (localAppData !== void 0 && localAppData.length > 0) {
332058
- candidates.push(`${localAppData}\\Programs\\Git\\bin\\bash.exe`);
332059
- candidates.push(`${localAppData}\\Programs\\Git\\usr\\bin\\bash.exe`);
332060
- }
332061
- for (const candidate of candidates) {
332062
- checked.push(candidate);
332063
- if (await deps.isFile(candidate)) return candidate;
332064
- }
332065
- 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(", ")}.`);
332066
- }
332067
- async function readGitExecPath(deps, gitExe) {
332068
- if (deps.platform === "win32" && !isAbsoluteWindowsPath(gitExe)) return void 0;
332069
- const stdout = await deps.execFileText(gitExe, ["--exec-path"], GIT_EXEC_PATH_TIMEOUT_MS);
332070
- if (stdout === void 0) return void 0;
332071
- for (const line of stdout.split(/\r?\n/)) {
332072
- const execPath = line.trim();
332073
- if (execPath.length > 0) return execPath;
332074
- }
332075
- }
332076
- function gitBashCandidatesFromGitExe(gitExe) {
332077
- const normalizedGitExe = path$11.win32.normalize(normalizeWindowsPath(gitExe));
332078
- const gitDir = path$11.win32.dirname(normalizedGitExe);
332079
- const gitDirName = path$11.win32.basename(gitDir).toLowerCase();
332080
- if (gitDirName !== "cmd" && gitDirName !== "bin") return;
332081
- return gitBashCandidatesFromGitRoot(path$11.win32.dirname(gitDir));
332082
- }
332083
- function gitBashCandidatesFromGitExecPath(execPath) {
332084
- const normalized = path$11.win32.normalize(normalizeWindowsPath(execPath));
332085
- const parts = normalized.split("\\");
332086
- for (let i = parts.length - 1; i >= 0; i -= 1) {
332087
- const segment = parts[i]?.toLowerCase();
332088
- if (segment === "mingw32" || segment === "mingw64") {
332089
- const root = parts.slice(0, i).join("\\");
332090
- if (root.length > 0) return gitBashCandidatesFromGitRoot(root);
332091
- }
332092
- }
332093
- return gitBashCandidatesFromGitRoot(path$11.win32.join(normalized, "..", ".."));
332094
- }
332095
- function gitBashCandidatesFromGitRoot(root) {
332096
- return [path$11.win32.normalize(path$11.win32.join(root, "bin", "bash.exe")), path$11.win32.normalize(path$11.win32.join(root, "usr", "bin", "bash.exe"))];
332097
- }
332098
- function normalizeWindowsPath(path) {
332099
- return path.replaceAll("/", "\\");
332100
- }
332101
- function isAbsoluteWindowsPath(path) {
332102
- return path$11.win32.isAbsolute(normalizeWindowsPath(path));
332103
- }
332104
- function dedupeWindowsPaths(paths) {
332105
- const deduped = [];
332106
- const seen = /* @__PURE__ */ new Set();
332107
- for (const path of paths) {
332108
- const key = normalizeWindowsPath(path).toLowerCase();
332109
- if (seen.has(key)) continue;
332110
- seen.add(key);
332111
- deduped.push(path);
332112
- }
332113
- return deduped;
332114
- }
332115
- /**
332116
- * Production convenience — derive the deps bag from Node's ambient surface.
332117
- *
332118
- * The result is memoised: subsequent calls return the original promise.
332119
- * `Environment` is immutable for the lifetime of the process (it derives
332120
- * from `process.platform`, `process.arch`, `os.release()`, and one-time
332121
- * shell-path discovery), so caching is sound. Tests that need to probe
332122
- * with different inputs should call {@link detectEnvironment} directly
332123
- * with an injected deps bag.
332124
- */
332125
- let detectedEnvironment;
332126
- function detectEnvironmentFromNode() {
332127
- if (detectedEnvironment !== void 0) return detectedEnvironment;
332128
- const platform = process.platform;
332129
- const env = process.env;
332130
- const isFile = async (path) => {
332131
- try {
332132
- await access(path, constants.F_OK);
332133
- return true;
332134
- } catch {
332135
- return false;
332136
- }
332137
- };
332138
- detectedEnvironment = detectEnvironment({
332139
- platform,
332140
- arch: process.arch,
332141
- release: nodeOs.release(),
332142
- env,
332143
- isFile,
332144
- execFileText
332145
- });
332146
- return detectedEnvironment;
332147
- }
332148
- async function findExecutablesOnPath(name, pathEnv, platform, isFile) {
332149
- if (pathEnv === void 0 || pathEnv.length === 0) return [];
332150
- const listSep = platform === "win32" ? ";" : ":";
332151
- const dirSep = platform === "win32" ? "\\" : "/";
332152
- const paths = [];
332153
- for (const rawDir of pathEnv.split(listSep)) {
332154
- const dir = rawDir.trim();
332155
- if (dir.length === 0) continue;
332156
- if (platform === "win32" && !isAbsoluteWindowsPath(dir)) continue;
332157
- const candidate = dir.endsWith(dirSep) ? `${dir}${name}` : `${dir}${dirSep}${name}`;
332158
- if (await isFile(candidate)) paths.push(candidate);
332159
- }
332160
- return platform === "win32" ? dedupeWindowsPaths(paths) : paths;
332161
- }
332162
- async function execFileText(file, args, timeoutMs) {
332163
- return new Promise((resolve) => {
332164
- execFile(file, [...args], {
332165
- encoding: "utf8",
332166
- timeout: timeoutMs,
332167
- windowsHide: true
332168
- }, (error, stdout) => {
332169
- if (error !== null) {
332170
- resolve(void 0);
332171
- return;
332172
- }
332173
- resolve(stdout);
332174
- });
332175
- });
332176
- }
332177
- //#endregion
332178
- //#region ../../packages/kaos/src/internal.ts
332179
- function isUtf8Continuation(byte) {
332180
- return byte >= 128 && byte <= 191;
332181
- }
332182
- function decodeUtf8Ignore(data) {
332183
- let output = "";
332184
- let i = 0;
332185
- while (i < data.length) {
332186
- const b0 = data[i];
332187
- if (b0 === void 0) break;
332188
- if (b0 <= 127) {
332189
- output += String.fromCodePoint(b0);
332190
- i += 1;
332191
- continue;
332192
- }
332193
- if (b0 >= 194 && b0 <= 223) {
332194
- const b1 = data[i + 1];
332195
- if (b1 !== void 0 && isUtf8Continuation(b1)) {
332196
- output += String.fromCodePoint((b0 & 31) << 6 | b1 & 63);
332197
- i += 2;
332198
- continue;
332199
- }
332200
- i += 1;
332201
- continue;
332202
- }
332203
- if (b0 >= 224 && b0 <= 239) {
332204
- const b1 = data[i + 1];
332205
- const b2 = data[i + 2];
332206
- 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)) {
332207
- output += String.fromCodePoint((b0 & 15) << 12 | (b1 & 63) << 6 | b2 & 63);
332208
- i += 3;
332209
- continue;
332210
- }
332211
- i += 1;
332212
- continue;
332213
- }
332214
- if (b0 >= 240 && b0 <= 244) {
332215
- const b1 = data[i + 1];
332216
- const b2 = data[i + 2];
332217
- const b3 = data[i + 3];
332218
- 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)) {
332219
- output += String.fromCodePoint((b0 & 7) << 18 | (b1 & 63) << 12 | (b2 & 63) << 6 | b3 & 63);
332220
- i += 4;
332221
- continue;
332222
- }
332223
- i += 1;
332224
- continue;
332225
- }
332226
- i += 1;
332227
- }
332228
- return output;
332229
- }
332230
- function decodeUtf16LeIgnore(data) {
332231
- let output = "";
332232
- let i = 0;
332233
- while (i + 1 < data.length) {
332234
- const first = data[i];
332235
- const second = data[i + 1];
332236
- if (first === void 0 || second === void 0) break;
332237
- const codeUnit = first | second << 8;
332238
- if (codeUnit >= 55296 && codeUnit <= 56319) {
332239
- const lowFirst = data[i + 2];
332240
- const lowSecond = data[i + 3];
332241
- if (lowFirst !== void 0 && lowSecond !== void 0) {
332242
- const low = lowFirst | lowSecond << 8;
332243
- if (low >= 56320 && low <= 57343) {
332244
- const codePoint = 65536 + (codeUnit - 55296 << 10) + (low - 56320);
332245
- output += String.fromCodePoint(codePoint);
332246
- i += 4;
332247
- continue;
332248
- }
332249
- }
332250
- i += 2;
332251
- continue;
332252
- }
332253
- if (codeUnit >= 56320 && codeUnit <= 57343) {
332254
- i += 2;
332255
- continue;
332256
- }
332257
- output += String.fromCodePoint(codeUnit);
332258
- i += 2;
332259
- }
332260
- return output;
332261
- }
332262
- /**
332263
- * Decode a Buffer into a string with Python-compatible `errors` handling.
332264
- *
332265
- * - `'strict'` (default): throw on invalid sequences (via TextDecoder `fatal: true`)
332266
- * - `'replace'`: substitute each invalid sequence with U+FFFD (TextDecoder default)
332267
- * - `'ignore'`: drop invalid input sequences while preserving valid U+FFFD characters
332268
- *
332269
- * Falls back to `Buffer.toString(encoding)` for encodings TextDecoder does not
332270
- * support (e.g. `hex`, `base64`, `binary`, `latin1`) — those are lossless
332271
- * byte-to-character mappings so `errors` has no effect.
332272
- * @internal
332273
- */
332274
- function decodeTextWithErrors(data, encoding, errors = "strict", ignoreBOM = false) {
332275
- let webLabel;
332276
- switch (encoding) {
332277
- case "utf-8":
332278
- case "utf8":
332279
- webLabel = "utf-8";
332280
- break;
332281
- case "utf16le":
332282
- case "ucs2":
332283
- case "ucs-2":
332284
- webLabel = "utf-16le";
332285
- break;
332286
- default: webLabel = void 0;
332287
- }
332288
- if (webLabel === void 0) return data.toString(encoding);
332289
- if (errors === "strict") return new TextDecoder(webLabel, {
332290
- fatal: true,
332291
- ignoreBOM
332292
- }).decode(data);
332293
- if (errors === "ignore") return webLabel === "utf-8" ? decodeUtf8Ignore(data) : decodeUtf16LeIgnore(data);
332294
- return new TextDecoder(webLabel, {
332295
- fatal: false,
332296
- ignoreBOM
332297
- }).decode(data);
332298
- }
332299
- /**
332300
- * Convert a glob pattern segment (e.g. "*.txt", "file?.log") into a RegExp.
332301
- * Mirrors Python pathlib behavior: includes dotfiles, case-sensitive by default.
332302
- * @internal
332303
- */
332304
- function globPatternToRegex(pattern, caseSensitive) {
332305
- let regex = "^";
332306
- for (let i = 0; i < pattern.length; i++) {
332307
- const ch = pattern[i];
332308
- if (ch === void 0) break;
332309
- switch (ch) {
332310
- case "*":
332311
- regex += "[^/]*";
332312
- break;
332313
- case "?":
332314
- regex += "[^/]";
332315
- break;
332316
- case "[": {
332317
- const end = pattern.indexOf("]", i + 1);
332318
- if (end === -1) regex += "\\[";
332319
- else {
332320
- let charClass = pattern.slice(i + 1, end);
332321
- charClass = charClass.replace(/\\/g, "\\\\");
332322
- if (charClass.startsWith("!")) charClass = "^" + charClass.slice(1);
332323
- else if (charClass.startsWith("^")) charClass = "\\" + charClass;
332324
- regex += "[" + charClass + "]";
332325
- i = end;
332326
- }
332327
- break;
332328
- }
332329
- case "\\":
332330
- if (i + 1 < pattern.length) {
332331
- const next = pattern.charAt(i + 1);
332332
- regex += next.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
332333
- i++;
332334
- } else regex += "\\\\";
332335
- break;
332336
- default: regex += ch.replaceAll(/[{}()+.\\[\]^$|]/g, "\\$&");
332337
- }
332338
- }
332339
- regex += "$";
332340
- return new RegExp(regex, caseSensitive ? "" : "i");
332341
- }
332342
- /**
332343
- * A Readable wrapper that preserves source backpressure while still allowing
332344
- * consumers to read buffered output after the source has ended.
332345
- * @internal
332346
- */
332347
- var BufferedReadable = class extends Readable {
332348
- _source;
332349
- _ended = false;
332350
- constructor(source) {
332351
- super({ highWaterMark: 128 * 1024 });
332352
- this._source = source;
332353
- this._source.on("data", this._onData);
332354
- this._source.on("end", this._onEnd);
332355
- this._source.on("close", this._onClose);
332356
- this._source.on("error", this._onError);
332357
- }
332358
- _read() {
332359
- if (!this._ended && !this.destroyed) this._source.resume();
332360
- }
332361
- _destroy(error, callback) {
332362
- this._source.off("data", this._onData);
332363
- this._source.off("end", this._onEnd);
332364
- this._source.off("close", this._onClose);
332365
- this._source.off("error", this._onError);
332366
- this._source.destroy();
332367
- callback(error);
332368
- }
332369
- _onData = (chunk) => {
332370
- if (!this.push(chunk)) this._source.pause();
332371
- };
332372
- _onEnd = () => {
332373
- this._ended = true;
332374
- this.push(null);
332375
- };
332376
- _onClose = () => {
332377
- if (!this._ended) {
332378
- this._ended = true;
332379
- this.push(null);
332380
- }
332381
- };
332382
- _onError = (error) => {
332383
- this.destroy(error);
332384
- };
332385
- };
332386
- //#endregion
332387
- //#region ../../packages/kaos/src/login-shell-path.ts
332388
- /**
332389
- * Login-shell PATH probe — enrich `process.env.PATH` with entries from the
332390
- * user's login shell.
332391
- *
332392
- * When blun is launched from a context that skipped the user's shell
332393
- * profile (GUI launchers, non-login parent shells), `process.env.PATH`
332394
- * misses entries like `/opt/homebrew/bin`, so commands spawned by the Bash
332395
- * tool can't find tools the user has in their interactive shell (e.g.
332396
- * `gh`). We run the user's login shell once (`$SHELL -l -c /usr/bin/env`),
332397
- * extract its PATH, and append the entries the current PATH lacks. Existing
332398
- * entries keep their order and priority; failures (no resolvable shell,
332399
- * hung or broken profile) silently leave PATH untouched.
332400
- *
332401
- * launchd/daemon launches can leave `$SHELL` unset or blank (see
332402
- * `defaultShell()` in agent-core's terminalService for the same case), so
332403
- * the probe falls back to the OS account's login shell from the user
332404
- * database before giving up.
332405
- *
332406
- * Like `detectEnvironment`, the probe is a pure function of injected deps
332407
- * so the suite runs identically on any host. Windows is skipped: the
332408
- * problem is specific to POSIX login-shell profiles.
332409
- */
332410
- const LOGIN_SHELL_ENV_TIMEOUT_MS = 5e3;
332411
- /**
332412
- * Run the user's login shell and return its PATH, or `undefined` when the
332413
- * probe does not apply (Windows, no resolvable shell) or fails (spawn
332414
- * error, timeout, no PATH in the output).
332415
- */
332416
- async function probeLoginShellPath(deps) {
332417
- if (deps.platform === "win32") return void 0;
332418
- const envShell = deps.env["SHELL"]?.trim();
332419
- const shell = envShell === void 0 || envShell.length === 0 ? deps.userShell() : envShell;
332420
- if (shell === void 0 || shell.length === 0) return void 0;
332421
- const stdout = await deps.execFileText(shell, [
332422
- "-l",
332423
- "-c",
332424
- "/usr/bin/env"
332425
- ], LOGIN_SHELL_ENV_TIMEOUT_MS);
332426
- if (stdout === void 0) return void 0;
332427
- let path;
332428
- for (const line of stdout.split("\n")) if (line.startsWith("PATH=")) path = line.slice(5).trim();
332429
- if (path === void 0 || path.length === 0) return void 0;
332430
- return path;
332431
- }
332432
- /**
332433
- * Union of the current PATH and the login-shell PATH: the current PATH
332434
- * string is kept verbatim — including empty components, which POSIX
332435
- * command lookup treats as the current directory — and login-shell
332436
- * entries the current PATH lacks are appended in their own order. When
332437
- * nothing is missing the current string is returned unchanged. Only
332438
- * absolute login-shell entries are imported: empty, `.`, and relative
332439
- * components are all cwd-dependent lookup, and appending one the user
332440
- * did not already have would widen their search path — LocalKaos runs
332441
- * commands from arbitrary workspace directories.
332442
- */
332443
- function mergeLoginShellPath(currentPath, loginShellPath) {
332444
- const current = currentPath ?? "";
332445
- const seen = new Set(current.split(":").filter((entry) => entry.length > 0));
332446
- const additions = [];
332447
- for (const entry of loginShellPath.split(":")) {
332448
- if (!entry.startsWith("/") || seen.has(entry)) continue;
332449
- seen.add(entry);
332450
- additions.push(entry);
332451
- }
332452
- if (additions.length === 0) return current;
332453
- if (currentPath === void 0) return additions.join(":");
332454
- return `${current}:${additions.join(":")}`;
332455
- }
332456
- /** Probe the login shell and merge its PATH into `deps.env['PATH']`. */
332457
- async function applyLoginShellPath(deps) {
332458
- const loginShellPath = await probeLoginShellPath(deps);
332459
- if (loginShellPath === void 0) return;
332460
- const currentPath = deps.env["PATH"];
332461
- const merged = mergeLoginShellPath(currentPath, loginShellPath);
332462
- if (merged === (currentPath ?? "")) return;
332463
- deps.env["PATH"] = merged;
332464
- }
332465
- /**
332466
- * Production convenience — apply the probe to `process.env` once per
332467
- * process. Memoised like `detectEnvironmentFromNode`: the login-shell PATH
332468
- * does not change for the lifetime of the process, and repeated
332469
- * `LocalKaos.create()` calls must not re-spawn the shell.
332470
- */
332471
- /**
332472
- * Login shell from the OS user database (`/etc/passwd` via getpwuid on
332473
- * Linux, Directory Services on macOS). `userInfo()` throws when the uid
332474
- * has no database entry (e.g. containers running an arbitrary uid), and
332475
- * service accounts may carry `/usr/sbin/nologin` — the latter needs no
332476
- * special casing here because probing it simply fails and degrades
332477
- * silently.
332478
- */
332479
- function userShellFromNode() {
332480
- try {
332481
- const shell = userInfo().shell;
332482
- return shell === null || shell.length === 0 ? void 0 : shell;
332483
- } catch {
332484
- return;
332485
- }
332486
- }
332487
- let appliedLoginShellPath;
332488
- function applyLoginShellPathFromNode() {
332489
- if (appliedLoginShellPath !== void 0) return appliedLoginShellPath;
332490
- appliedLoginShellPath = applyLoginShellPath({
332491
- platform: process.platform,
332492
- env: process.env,
332493
- userShell: userShellFromNode,
332494
- execFileText
332495
- });
332496
- return appliedLoginShellPath;
332497
- }
332498
- //#endregion
332499
- //#region ../../packages/kaos/src/local.ts
332500
- const isWindows$1 = process.platform === "win32";
332501
- const READ_CHUNK_SIZE = 64 * 1024;
332502
- /**
332503
- * Build the `(dev, ino)` cycle-detection key used by `_globWalk`'s
332504
- * visited set. Returns `null` when `ino` is 0, which Node returns on
332505
- * filesystems that don't carry inodes (Windows FAT/exFAT, some SMB/NFS
332506
- * mounts). A null key signals "no reliable identity for this dir" so
332507
- * the caller skips visited tracking for that descent — cycle safety
332508
- * is weakened on those filesystems, but normal walking works instead
332509
- * of every directory colliding on the shared key `"<dev>:0"`.
332510
- */
332511
- function cycleKey(s) {
332512
- if (s.ino === 0) return null;
332513
- return `${String(s.dev)}:${String(s.ino)}`;
332514
- }
332515
- function buildLocalSpawnOptions(_isWindows, cwd, env) {
332516
- return {
332517
- cwd,
332518
- env,
332519
- stdio: [
332520
- "pipe",
332521
- "pipe",
332522
- "pipe"
332523
- ],
332524
- detached: true,
332525
- windowsHide: true
332526
- };
332527
- }
332528
- var LocalProcess = class {
332529
- stdin;
332530
- stdout;
332531
- stderr;
332532
- pid;
332533
- _child;
332534
- _exitCode = null;
332535
- _exitPromise;
332536
- _disposed = false;
332537
- constructor(child) {
332538
- if (child.stdin === null || child.stdout === null || child.stderr === null) throw new Error("Process must be created with stdin/stdout/stderr pipes.");
332539
- this._child = child;
332540
- this.stdin = child.stdin;
332541
- this.stdout = new BufferedReadable(child.stdout);
332542
- this.stderr = new BufferedReadable(child.stderr);
332543
- this.pid = child.pid ?? -1;
332544
- this._exitPromise = new Promise((resolve, reject) => {
332545
- child.on("exit", (code) => {
332546
- this._exitCode = code ?? -1;
332547
- resolve(this._exitCode);
332548
- });
332549
- child.on("error", (error) => {
332550
- reject(error);
332551
- });
332552
- });
332553
- }
332554
- get exitCode() {
332555
- return this._exitCode;
332556
- }
332557
- async wait() {
332558
- return this._exitPromise;
332559
- }
332560
- kill(signal) {
332561
- if (this.pid <= 0) return Promise.resolve();
332562
- if (isWindows$1) {
332563
- const taskkillArgs = [
332564
- "/T",
332565
- "/F",
332566
- "/PID",
332567
- String(this.pid)
332568
- ];
332569
- return new Promise((resolve) => {
332570
- const killer = spawn("taskkill", taskkillArgs, {
332571
- stdio: "ignore",
332572
- windowsHide: true
332573
- });
332574
- const done = () => {
332575
- resolve();
332576
- };
332577
- killer.once("error", done);
332578
- killer.once("close", done);
332579
- });
332580
- }
332581
- try {
332582
- process.kill(-this.pid, signal ?? "SIGTERM");
332583
- } catch (error) {
332584
- const err = error;
332585
- if (err.code === "ESRCH") return Promise.resolve();
332586
- if (err.code === "EPERM") {
332587
- try {
332588
- this._child.kill(signal ?? "SIGTERM");
332589
- } catch {}
332590
- return Promise.resolve();
332591
- }
332592
- throw error;
332593
- }
332594
- return Promise.resolve();
332595
- }
332596
- dispose() {
332597
- if (this._disposed) return;
332598
- this._disposed = true;
332599
- this.stdin.destroy();
332600
- this.stdout.destroy();
332601
- this.stderr.destroy();
332602
- }
332603
- };
332604
- /**
332605
- * A KAOS implementation that directly interacts with the local filesystem.
332606
- *
332607
- * Note: LocalKaos maintains its own per-instance working directory (`_cwd`)
332608
- * rather than mutating `process.cwd()`. This lets multiple LocalKaos instances
332609
- * coexist with independent cwds (e.g. when switching contexts via
332610
- * `runWithKaos`) without cross-polluting each other's relative-path resolution.
332611
- */
332612
- var LocalKaos = class LocalKaos {
332613
- name = "local";
332614
- osEnv;
332615
- _cwd;
332616
- _envLayers;
332617
- constructor(osEnv, cwd, envLayers = []) {
332618
- this._cwd = normalize$3(cwd ?? process.cwd());
332619
- this.osEnv = osEnv;
332620
- this._envLayers = envLayers;
332621
- }
332622
- /**
332623
- * Construct a fresh `LocalKaos` after probing the host environment.
332624
- *
332625
- * Each call returns a new instance with its own `_cwd`; concurrent
332626
- * callers can therefore operate on independent working directories
332627
- * without polluting one another.
332628
- */
332629
- static async create() {
332630
- const [osEnv] = await Promise.all([detectEnvironmentFromNode(), applyLoginShellPathFromNode()]);
332631
- return new LocalKaos(osEnv);
332632
- }
332633
- withCwd(cwd) {
332634
- return new LocalKaos(this.osEnv, cwd, this._envLayers);
332635
- }
332636
- withEnv(env) {
332637
- return new LocalKaos(this.osEnv, this._cwd, [...this._envLayers, env]);
332638
- }
332639
- _resolvePath(path) {
332640
- if (isAbsolute$1(path)) return normalize$3(path);
332641
- return join$1(this._cwd, path);
332642
- }
332643
- pathClass() {
332644
- return isWindows$1 ? "win32" : "posix";
332645
- }
332646
- normpath(path) {
332647
- return normalize$3(path);
332648
- }
332649
- gethome() {
332650
- return normalize$3(homedir());
332651
- }
332652
- getcwd() {
332653
- return this._cwd;
332654
- }
332655
- /**
332656
- * Change the working directory of this LocalKaos instance.
332657
- *
332658
- * Unlike Python's `os.chdir`, this is instance-scoped and never touches
332659
- * `process.cwd()`. Child processes spawned via {@link exec} inherit this
332660
- * instance's `_cwd`; concurrent LocalKaos instances each carry their own
332661
- * independent cwd. If you need Python-compatible process-global cwd,
332662
- * call `process.chdir(x)` directly.
332663
- */
332664
- async chdir(path) {
332665
- const resolved = this._resolvePath(path);
332666
- if (!(await stat(resolved)).isDirectory()) throw new Error(`Not a directory: ${resolved}`);
332667
- this._cwd = resolved;
332668
- }
332669
- async stat(path, options) {
332670
- const resolved = this._resolvePath(path);
332671
- const s = options?.followSymlinks ?? true ? await stat(resolved) : await lstat(resolved);
332672
- return {
332673
- stMode: s.mode,
332674
- stIno: s.ino,
332675
- stDev: s.dev,
332676
- stNlink: s.nlink,
332677
- stUid: s.uid,
332678
- stGid: s.gid,
332679
- stSize: s.size,
332680
- stAtime: s.atimeMs / 1e3,
332681
- stMtime: s.mtimeMs / 1e3,
332682
- stCtime: isWindows$1 ? s.birthtimeMs / 1e3 : s.ctimeMs / 1e3
332683
- };
332684
- }
332685
- async *iterdir(path) {
332686
- const resolved = this._resolvePath(path);
332687
- const entries = await readdir(resolved);
332688
- for (const entry of entries) yield join$1(resolved, entry);
332689
- }
332690
- async *glob(path, pattern, options) {
332691
- const resolved = this._resolvePath(path);
332692
- const caseSensitive = options?.caseSensitive ?? true;
332693
- const patternParts = pattern.split("/");
332694
- const initVisited = /* @__PURE__ */ new Set();
332695
- try {
332696
- const rootKey = cycleKey(await stat(resolved));
332697
- if (rootKey !== null) initVisited.add(rootKey);
332698
- } catch {}
332699
- yield* this._globWalk(resolved, patternParts, caseSensitive, initVisited);
332700
- }
332701
- async *_globWalk(basePath, patternParts, caseSensitive, visited) {
332702
- if (patternParts.length === 0) return;
332703
- const [currentPattern, ...remainingParts] = patternParts;
332704
- if (currentPattern === "**") {
332705
- if (remainingParts.length > 0) yield* this._globWalk(basePath, remainingParts, caseSensitive, visited);
332706
- else yield basePath;
332707
- let entries;
332708
- try {
332709
- entries = await readdir(basePath);
332710
- } catch {
332711
- return;
332712
- }
332713
- for (const entry of entries) {
332714
- const fullPath = join$1(basePath, entry);
332715
- let entryStat;
332716
- try {
332717
- entryStat = await stat(fullPath);
332718
- } catch {
332719
- continue;
332720
- }
332721
- if (entryStat.isDirectory()) {
332722
- const key = cycleKey(entryStat);
332723
- if (key !== null && visited.has(key)) continue;
332724
- yield* this._globWalk(fullPath, patternParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
332725
- } else if (remainingParts.length === 0) yield fullPath;
332726
- }
332727
- } else {
332728
- const regex = globPatternToRegex(currentPattern ?? "", caseSensitive);
332729
- let entries;
332730
- try {
332731
- entries = await readdir(basePath);
332732
- } catch {
332733
- return;
332734
- }
332735
- for (const entry of entries) {
332736
- if (!regex.test(entry)) continue;
332737
- const fullPath = join$1(basePath, entry);
332738
- if (remainingParts.length === 0) yield fullPath;
332739
- else {
332740
- let entryStat;
332741
- try {
332742
- entryStat = await stat(fullPath);
332743
- } catch {
332744
- continue;
332745
- }
332746
- if (entryStat.isDirectory()) {
332747
- const key = cycleKey(entryStat);
332748
- if (key !== null && visited.has(key)) continue;
332749
- yield* this._globWalk(fullPath, remainingParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
332750
- }
332751
- }
332752
- }
332753
- }
332754
- }
332755
- async readBytes(path, n) {
332756
- const resolved = this._resolvePath(path);
332757
- if (n === void 0) return Buffer.from(await readFile(resolved));
332758
- const fh = await open(resolved, "r");
332759
- try {
332760
- const buf = Buffer.alloc(n);
332761
- const { bytesRead } = await fh.read(buf, 0, n, 0);
332762
- return buf.subarray(0, bytesRead);
332763
- } finally {
332764
- await fh.close();
332765
- }
332766
- }
332767
- async readText(path, options) {
332768
- const resolved = this._resolvePath(path);
332769
- const encoding = options?.encoding ?? "utf-8";
332770
- const errors = options?.errors ?? "strict";
332771
- return decodeTextWithErrors(await readFile(resolved), encoding, errors);
332772
- }
332773
- async *readLines(path, options) {
332774
- const resolved = this._resolvePath(path);
332775
- const encoding = options?.encoding ?? "utf-8";
332776
- const errors = options?.errors ?? "strict";
332777
- if (!isUtf8Encoding(encoding)) {
332778
- yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readFile(resolved), encoding, errors));
332779
- return;
332780
- }
332781
- yield* this._readUtf8Lines(resolved, errors);
332782
- }
332783
- async scanTextFile(path) {
332784
- const fh = await open(this._resolvePath(path), "r");
332785
- try {
332786
- const buf = Buffer.alloc(READ_CHUNK_SIZE);
332787
- const flags = {
332788
- hasCrLf: false,
332789
- hasLf: false,
332790
- hasLoneCr: false
332791
- };
332792
- const validator = createUtf8Validator();
332793
- let totalLines = 0;
332794
- let totalBytes = 0;
332795
- let endsWithNewline = false;
332796
- let hasNul = false;
332797
- let prevWasCr = false;
332798
- while (true) {
332799
- const { bytesRead } = await fh.read(buf, 0, buf.length, null);
332800
- if (bytesRead === 0) break;
332801
- const chunk = buf.subarray(0, bytesRead);
332802
- validator.write(chunk);
332803
- for (let i = 0; i < chunk.length; i += 1) {
332804
- const byte = chunk[i];
332805
- if (byte === void 0) continue;
332806
- if (byte === 0) hasNul = true;
332807
- if (byte === 10) totalLines += 1;
332808
- }
332809
- prevWasCr = updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr);
332810
- totalBytes += bytesRead;
332811
- endsWithNewline = chunk[bytesRead - 1] === 10;
332812
- }
332813
- if (prevWasCr) flags.hasLoneCr = true;
332814
- validator.end();
332815
- if (totalBytes > 0 && !endsWithNewline) totalLines += 1;
332816
- return {
332817
- totalLines,
332818
- endsWithNewline,
332819
- hasNul,
332820
- lineEndingFlags: flags
332821
- };
332822
- } finally {
332823
- await fh.close();
332824
- }
332825
- }
332826
- async *readLineRange(path, options) {
332827
- const resolved = this._resolvePath(path);
332828
- const errors = options.errors ?? "strict";
332829
- yield* this._readUtf8Lines(resolved, errors, {
332830
- startLine: options.startLine,
332831
- maxLines: options.maxLines
332832
- });
332833
- }
332834
- async *readTailLines(path, options) {
332835
- if (options.tailCount <= 0) return;
332836
- const resolved = this._resolvePath(path);
332837
- const errors = options.errors ?? "strict";
332838
- const fh = await open(resolved, "r");
332839
- try {
332840
- const s = await fh.stat();
332841
- if (s.size === 0) return;
332842
- let pos = s.size;
332843
- let foundLf = 0;
332844
- let startOffset = 0;
332845
- let needLf = options.tailCount;
332846
- let sawTailBlock = false;
332847
- while (pos > 0 && foundLf < needLf) {
332848
- const readSize = Math.min(READ_CHUNK_SIZE, pos);
332849
- pos -= readSize;
332850
- const buf = Buffer.alloc(readSize);
332851
- await fh.read(buf, 0, readSize, pos);
332852
- if (!sawTailBlock) {
332853
- sawTailBlock = true;
332854
- needLf = buf[readSize - 1] === 10 ? options.tailCount + 1 : options.tailCount;
332855
- }
332856
- for (let i = readSize - 1; i >= 0; i -= 1) {
332857
- if (buf[i] !== 10) continue;
332858
- foundLf += 1;
332859
- if (foundLf === needLf) {
332860
- startOffset = pos + i + 1;
332861
- break;
332862
- }
332863
- }
332864
- }
332865
- if (foundLf < needLf) startOffset = 0;
332866
- yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readRange(fh, startOffset, s.size - startOffset), "utf-8", errors, startOffset !== 0));
332867
- } finally {
332868
- await fh.close();
332869
- }
332870
- }
332871
- async *_readUtf8Lines(resolved, errors, range) {
332872
- const startLine = range?.startLine ?? 1;
332873
- const maxLines = range?.maxLines ?? Number.POSITIVE_INFINITY;
332874
- const fh = await open(resolved, "r");
332875
- try {
332876
- const buf = Buffer.alloc(READ_CHUNK_SIZE);
332877
- let pending = [];
332878
- let pendingOffset = 0;
332879
- let fileOffset = 0;
332880
- let lineNo = 1;
332881
- let yielded = 0;
332882
- while (true) {
332883
- const { bytesRead } = await fh.read(buf, 0, buf.length, null);
332884
- if (bytesRead === 0) break;
332885
- const chunk = buf.subarray(0, bytesRead);
332886
- let lineStart = 0;
332887
- for (let i = 0; i < chunk.length; i += 1) {
332888
- if (chunk[i] !== 10) continue;
332889
- const piece = chunk.subarray(lineStart, i + 1);
332890
- const lineOffset = pending.length === 0 ? fileOffset + lineStart : pendingOffset;
332891
- const line = pending.length === 0 ? piece : Buffer.concat([...pending, piece]);
332892
- if (lineNo >= startLine) {
332893
- yield decodeTextWithErrors(line, "utf-8", errors, lineOffset !== 0);
332894
- yielded += 1;
332895
- if (yielded >= maxLines) return;
332896
- }
332897
- pending = [];
332898
- lineStart = i + 1;
332899
- lineNo += 1;
332900
- }
332901
- if (lineStart < chunk.length) {
332902
- const tail = Buffer.from(chunk.subarray(lineStart));
332903
- if (pending.length === 0) pendingOffset = fileOffset + lineStart;
332904
- pending.push(tail);
332905
- }
332906
- fileOffset += bytesRead;
332907
- }
332908
- if (pending.length > 0) {
332909
- const line = Buffer.concat(pending);
332910
- if (lineNo >= startLine) yield decodeTextWithErrors(line, "utf-8", errors, pendingOffset !== 0);
332911
- }
332912
- } finally {
332913
- await fh.close();
332914
- }
332915
- }
332916
- async writeBytes(path, data) {
332917
- await writeFile(this._resolvePath(path), data);
332918
- return data.length;
332919
- }
332920
- async writeText(path, data, options) {
332921
- const resolved = this._resolvePath(path);
332922
- const encoding = options?.encoding ?? "utf-8";
332923
- if ((options?.mode ?? "w") === "a") await appendFile(resolved, data, encoding);
332924
- else await writeFile(resolved, data, encoding);
332925
- return data.length;
332926
- }
332927
- async mkdir(path, options) {
332928
- const resolved = this._resolvePath(path);
332929
- const parents = options?.parents ?? false;
332930
- const existOk = options?.existOk ?? false;
332931
- if (parents) {
332932
- if (!existOk) try {
332933
- if ((await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists`);
332934
- } catch (error) {
332935
- if (error instanceof KaosFileExistsError) throw error;
332936
- if (error.code !== "ENOENT") throw error;
332937
- }
332938
- await mkdir(resolved, { recursive: true });
332939
- return;
332940
- }
332941
- try {
332942
- await mkdir(resolved);
332943
- } catch (error) {
332944
- if (existOk && error instanceof Error && "code" in error && error.code === "EEXIST") {
332945
- if (!(await stat(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists but is not a directory`);
332946
- return;
332947
- }
332948
- throw error;
332949
- }
332950
- }
332951
- async exec(...args) {
332952
- const command = args[0];
332953
- if (command === void 0) throw new Error("LocalKaos.exec(): at least one argument (the command to run) is required.");
332954
- const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv()));
332955
- await waitForSpawn(child);
332956
- return new LocalProcess(child);
332957
- }
332958
- async execWithEnv(args, env) {
332959
- const command = args[0];
332960
- if (command === void 0) throw new Error("LocalKaos.execWithEnv(): at least one argument (the command to run) is required.");
332961
- const child = spawn(command, args.slice(1), buildLocalSpawnOptions(isWindows$1, this._cwd, this._buildExecEnv(env)));
332962
- await waitForSpawn(child);
332963
- return new LocalProcess(child);
332964
- }
332965
- _buildExecEnv(invocationEnv) {
332966
- if (this._envLayers.length === 0) return invocationEnv;
332967
- const merged = {
332968
- ...process.env,
332969
- ...invocationEnv
332970
- };
332971
- for (const layer of this._envLayers) Object.assign(merged, layer);
332972
- return merged;
332973
- }
332974
- };
332975
- function isUtf8Encoding(encoding) {
332976
- return encoding === "utf-8" || encoding === "utf8";
332977
- }
332978
- function* splitLinesKeepingTerminator(text) {
332979
- if (text.length === 0) return;
332980
- let start = 0;
332981
- for (let i = 0; i < text.length; i += 1) if (text.codePointAt(i) === 10) {
332982
- yield text.slice(start, i + 1);
332983
- start = i + 1;
332984
- }
332985
- if (start < text.length) yield text.slice(start);
332986
- }
332987
- function updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr) {
332988
- for (let i = 0; i < chunk.length; i += 1) {
332989
- const byte = chunk[i];
332990
- if (byte === void 0) continue;
332991
- if (byte === 13) {
332992
- if (prevWasCr) flags.hasLoneCr = true;
332993
- prevWasCr = true;
332994
- } else if (byte === 10) {
332995
- if (prevWasCr) flags.hasCrLf = true;
332996
- else flags.hasLf = true;
332997
- prevWasCr = false;
332998
- } else {
332999
- if (prevWasCr) flags.hasLoneCr = true;
333000
- prevWasCr = false;
333001
- }
333002
- }
333003
- return prevWasCr;
333004
- }
333005
- function createUtf8Validator() {
333006
- let needed = 0;
333007
- let lower = 128;
333008
- let upper = 191;
333009
- const fail = () => {
333010
- throw new TypeError("Invalid UTF-8 data");
333011
- };
333012
- return {
333013
- write(chunk) {
333014
- for (let i = 0; i < chunk.length; i += 1) {
333015
- const byte = chunk[i];
333016
- if (byte === void 0) continue;
333017
- if (needed === 0) {
333018
- if (byte <= 127) continue;
333019
- if (byte >= 194 && byte <= 223) needed = 1;
333020
- else if (byte === 224) {
333021
- needed = 2;
333022
- lower = 160;
333023
- } else if (byte >= 225 && byte <= 236) needed = 2;
333024
- else if (byte === 237) {
333025
- needed = 2;
333026
- upper = 159;
333027
- } else if (byte >= 238 && byte <= 239) needed = 2;
333028
- else if (byte === 240) {
333029
- needed = 3;
333030
- lower = 144;
333031
- } else if (byte >= 241 && byte <= 243) needed = 3;
333032
- else if (byte === 244) {
333033
- needed = 3;
333034
- upper = 143;
333035
- } else fail();
333036
- } else {
333037
- if (byte < lower || byte > upper) fail();
333038
- lower = 128;
333039
- upper = 191;
333040
- needed -= 1;
333041
- }
333042
- }
333043
- },
333044
- end() {
333045
- if (needed !== 0) fail();
333046
- }
333047
- };
333048
- }
333049
- async function readRange(fh, start, length) {
333050
- const data = Buffer.alloc(length);
333051
- let offset = 0;
333052
- while (offset < length) {
333053
- const { bytesRead } = await fh.read(data, offset, length - offset, start + offset);
333054
- if (bytesRead === 0) break;
333055
- offset += bytesRead;
333056
- }
333057
- return offset === length ? data : data.subarray(0, offset);
333058
- }
333059
- function waitForSpawn(child) {
333060
- return new Promise((resolve, reject) => {
333061
- const onSpawn = () => {
333062
- child.off("error", onError);
333063
- resolve();
333064
- };
333065
- const onError = (err) => {
333066
- child.off("spawn", onSpawn);
333067
- reject(err);
333068
- };
333069
- child.once("spawn", onSpawn);
333070
- child.once("error", onError);
333071
- });
333072
- }
333073
- new AsyncLocalStorage();
333074
- //#endregion
333075
333441
  //#region ../../packages/agent-core/src/agent/replay/build.ts
333076
333442
  async function buildReplay(persistence, range) {
333077
333443
  const agent = new Agent$1({