@wrongstack/tools 0.82.6 → 0.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bash.js CHANGED
@@ -432,7 +432,7 @@ function getProcessRegistry() {
432
432
 
433
433
  // src/bash.ts
434
434
  var MAX_OUTPUT = 32768;
435
- var DEFAULT_TIMEOUT = 3e4;
435
+ var DEFAULT_TIMEOUT_MS = 3e4;
436
436
  var STREAM_FLUSH_INTERVAL_MS = 200;
437
437
  var STREAM_FLUSH_BYTES = 4 * 1024;
438
438
  var bashTool = {
@@ -495,9 +495,19 @@ var bashTool = {
495
495
  };
496
496
  return;
497
497
  }
498
- const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT, 6e5));
498
+ const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS, 6e5));
499
499
  const isWin = os.platform() === "win32";
500
- const shell = isWin ? process.env["COMSPEC"] ?? "cmd.exe" : process.env["SHELL"] ?? "/bin/bash";
500
+ const shell = (() => {
501
+ const explicit = process.env[isWin ? "WRONGSTACK_COMSPEC" : "WRONGSTACK_SHELL"];
502
+ if (explicit) return explicit;
503
+ if (isWin) return process.env["COMSPEC"] ?? "cmd.exe";
504
+ const fromEnv = process.env["SHELL"];
505
+ if (fromEnv) {
506
+ const name = fromEnv.split("/").pop() ?? "";
507
+ if (["bash", "zsh", "sh", "dash", "fish"].includes(name)) return fromEnv;
508
+ }
509
+ return "/bin/bash";
510
+ })();
501
511
  const args = isWin ? ["/c", input.command] : ["-c", input.command];
502
512
  const env = buildChildEnv(ctx.session?.id);
503
513
  const detached = isWin ? !!input.background : true;
package/dist/bash.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/_util.ts","../src/circuit-breaker.ts","../src/process-registry.ts","../src/bash.ts"],"names":["expectDefined","os2","buf","child","pid"],"mappings":";;;;;;AAOA,SAAS,cAAiB,KAAA,EAAgC;AACxD,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,EAAW;AACzC,IAAA,MAAM,IAAI,MAAM,8BAA8B,CAAA;AAAA,EAChD;AACA,EAAA,OAAO,KAAA;AACT;AAwHO,IAAM,wBAAA,GAA2B,KAAA;AAGxC,IAAM,oBAAA,GAAuB,CAAA;AAQtB,SAAS,wBAAwB,IAAA,EAAsB;AAC5D,EAAA,MAAM,EAAA,GAAK,IAAA,CAAK,OAAA,CAAQ,OAAA,EAAS,IAAI,CAAA;AACrC,EAAA,IAAI,CAAC,EAAA,CAAG,QAAA,CAAS,IAAI,GAAG,OAAO,EAAA;AAC/B,EAAA,OAAO,EAAA,CACJ,MAAM,IAAI,CAAA,CACV,IAAI,CAAC,IAAA,KAAU,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA,GAAI,KAAK,KAAA,CAAM,IAAA,CAAK,YAAY,IAAI,CAAA,GAAI,CAAC,CAAA,GAAI,IAAK,CAAA,CACnF,IAAA,CAAK,IAAI,CAAA;AACd;AAOO,SAAS,6BAAA,CAA8B,IAAA,EAAc,MAAA,GAAS,oBAAA,EAA8B;AACjG,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC7B,EAAA,MAAM,MAAgB,EAAC;AACvB,EAAA,IAAI,CAAA,GAAI,CAAA;AACR,EAAA,OAAO,CAAA,GAAI,MAAM,MAAA,EAAQ;AACvB,IAAA,IAAI,IAAI,CAAA,GAAI,CAAA;AACZ,IAAA,OAAO,CAAA,GAAI,MAAM,MAAA,IAAU,KAAA,CAAM,CAAC,CAAA,KAAM,KAAA,CAAM,CAAC,CAAA,EAAG,CAAA,EAAA;AAClD,IAAA,MAAM,MAAM,CAAA,GAAI,CAAA;AAChB,IAAA,IAAI,OAAO,MAAA,EAAQ;AACjB,MAAA,GAAA,CAAI,IAAA,CAAK,cAAc,KAAA,CAAM,CAAC,CAAC,CAAA,EAAG,CAAA,sBAAA,EAAe,GAAG,CAAA,UAAA,CAAI,CAAA;AAAA,IAC1D,CAAA,MAAO;AACL,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,EAAA,EAAK,GAAA,CAAI,IAAA,CAAK,aAAA,CAAc,KAAA,CAAM,CAAC,CAAC,CAAC,CAAA;AAAA,IAC9D;AACA,IAAA,CAAA,GAAI,CAAA;AAAA,EACN;AACA,EAAA,OAAO,GAAA,CAAI,KAAK,IAAI,CAAA;AACtB;AAGA,SAAS,aAAA,CAAc,GAAW,QAAA,EAA0B;AAC1D,EAAA,IAAI,QAAA,IAAY,GAAG,OAAO,EAAA;AAC1B,EAAA,IAAI,OAAO,UAAA,CAAW,CAAA,EAAG,MAAM,CAAA,IAAK,UAAU,OAAO,CAAA;AACrD,EAAA,IAAI,EAAA,GAAK,CAAA;AACT,EAAA,IAAI,KAAK,CAAA,CAAE,MAAA;AACX,EAAA,OAAO,KAAK,EAAA,EAAI;AACd,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,IAAA,CAAA,CAAM,EAAA,GAAK,MAAM,CAAC,CAAA;AACnC,IAAA,IAAI,MAAA,CAAO,UAAA,CAAW,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,EAAG,MAAM,CAAA,IAAK,QAAA,EAAU,EAAA,GAAK,GAAA;AAAA,cACvD,GAAA,GAAM,CAAA;AAAA,EAClB;AACA,EAAA,OAAO,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AACtB;AAGA,SAAS,aAAA,CAAc,GAAW,QAAA,EAA0B;AAC1D,EAAA,IAAI,QAAA,IAAY,GAAG,OAAO,EAAA;AAC1B,EAAA,IAAI,OAAO,UAAA,CAAW,CAAA,EAAG,MAAM,CAAA,IAAK,UAAU,OAAO,CAAA;AACrD,EAAA,IAAI,EAAA,GAAK,CAAA;AACT,EAAA,IAAI,KAAK,CAAA,CAAE,MAAA;AACX,EAAA,OAAO,KAAK,EAAA,EAAI;AACd,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,IAAA,CAAA,CAAM,EAAA,GAAK,MAAM,CAAC,CAAA;AACnC,IAAA,IAAI,MAAA,CAAO,UAAA,CAAW,CAAA,CAAE,KAAA,CAAM,CAAA,CAAE,MAAA,GAAS,GAAG,CAAA,EAAG,MAAM,CAAA,IAAK,QAAA,EAAU,EAAA,GAAK,GAAA;AAAA,cAC/D,GAAA,GAAM,CAAA;AAAA,EAClB;AACA,EAAA,OAAO,CAAA,CAAE,KAAA,CAAM,CAAA,CAAE,MAAA,GAAS,EAAE,CAAA;AAC9B;AAOO,SAAS,gBAAA,CAAiB,GAAW,QAAA,EAA0B;AACpE,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,UAAA,CAAW,CAAA,EAAG,MAAM,CAAA;AACzC,EAAA,IAAI,KAAA,IAAS,UAAU,OAAO,CAAA;AAG9B,EAAA,MAAM,cAAA,GAAiB,EAAA;AACvB,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,WAAW,cAAc,CAAA;AACnD,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,IAAI,CAAA;AAC1C,EAAA,MAAM,IAAA,GAAO,aAAA,CAAc,CAAA,EAAG,UAAU,CAAA;AACxC,EAAA,MAAM,IAAA,GAAO,cAAc,CAAA,EAAG,KAAA,GAAQ,OAAO,UAAA,CAAW,IAAA,EAAM,MAAM,CAAC,CAAA;AACrE,EAAA,MAAM,IAAA,GAAO,OAAO,UAAA,CAAW,IAAA,EAAM,MAAM,CAAA,GAAI,MAAA,CAAO,UAAA,CAAW,IAAA,EAAM,MAAM,CAAA;AAC7E,EAAA,OAAO,GAAG,IAAI;AAAA,iBAAA,EAAiB,QAAQ,IAAI,CAAA;AAAA,EAAa,IAAI,CAAA,CAAA;AAC9D;AAOO,SAAS,sBAAA,CACd,GAAA,EACA,IAAA,GAA0C,EAAC,EACnC;AACR,EAAA,IAAI,CAAC,KAAK,OAAO,GAAA;AACjB,EAAA,IAAI,IAAA,GAAY,eAAU,GAAG,CAAA;AAC7B,EAAA,IAAA,GAAO,wBAAwB,IAAI,CAAA;AACnC,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,EAAE,CAAA;AACnC,EAAA,IAAA,GAAO,8BAA8B,IAAI,CAAA;AACzC,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,MAAM,CAAA;AACrC,EAAA,OAAO,gBAAA,CAAiB,IAAA,EAAM,IAAA,CAAK,QAAA,IAAY,wBAAwB,CAAA;AACzE;;;ACjLA,IAAM,gCAAA,GAAmC,CAAA;AACzC,IAAM,8BAAA,GAAiC,GAAA;AACvC,IAAM,sBAAA,GAAyB,CAAA;AAC/B,IAAM,iBAAA,GAAoB,GAAA;AAC1B,IAAM,4BAAA,GAA+B,EAAA;AACrC,IAAM,mBAAA,GAAsB,GAAA;AAarB,IAAM,iBAAN,MAAqB;AAAA,EACT,sBAAA;AAAA,EACA,mBAAA;AAAA,EACA,YAAA;AAAA,EACA,QAAA;AAAA,EACA,iBAAA;AAAA,EACA,UAAA;AAAA,EAET,KAAA,GAAsB,QAAA;AAAA,EACtB,mBAAA,GAAsB,CAAA;AAAA,EACtB,SAAuB,EAAC;AAAA,EACxB,aAAA,GAA+B,IAAA;AAAA,EAC/B,UAAA,GAA4B,IAAA;AAAA;AAAA,EAE5B,QAAA,GAA0B,IAAA;AAAA,EAElC,WAAA,CAAY,MAAA,GAA+B,EAAC,EAAG;AAC7C,IAAA,IAAA,CAAK,sBAAA,GAAyB,OAAO,sBAAA,IAA0B,gCAAA;AAC/D,IAAA,IAAA,CAAK,mBAAA,GAAsB,OAAO,mBAAA,IAAuB,8BAAA;AACzD,IAAA,IAAA,CAAK,YAAA,GAAe,OAAO,YAAA,IAAgB,sBAAA;AAC3C,IAAA,IAAA,CAAK,QAAA,GAAW,OAAO,QAAA,IAAY,iBAAA;AACnC,IAAA,IAAA,CAAK,iBAAA,GAAoB,OAAO,iBAAA,IAAqB,4BAAA;AACrD,IAAA,IAAA,CAAK,UAAA,GAAa,OAAO,UAAA,IAAc,mBAAA;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,UAAA,GAAsB;AACxB,IAAA,IAAA,CAAK,qBAAA,EAAsB;AAC3B,IAAA,OAAO,KAAK,KAAA,KAAU,MAAA;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,GAAmC;AACjC,IAAA,IAAA,CAAK,qBAAA,EAAsB;AAC3B,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,IAAI,iBAAA,GAAmC,IAAA;AACvC,IAAA,IAAI,IAAA,CAAK,QAAA,KAAa,IAAA,IAAQ,IAAA,CAAK,UAAU,MAAA,EAAQ;AACnD,MAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,QAAA;AAC3B,MAAA,iBAAA,GAAoB,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,aAAa,OAAO,CAAA;AAAA,IAC3D;AACA,IAAA,OAAO;AAAA,MACL,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,qBAAqB,IAAA,CAAK,mBAAA;AAAA,MAC1B,iBAAA,EAAmB,KAAK,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAA,CAAE,MAAA;AAAA,MACrD,aAAA,EAAe,KAAK,MAAA,CAAO,MAAA;AAAA,MAC3B,UAAU,IAAA,CAAK,QAAA;AAAA,MACf,mBAAA,EAAqB,iBAAA;AAAA,MACrB,eAAe,IAAA,CAAK,aAAA;AAAA,MACpB,YAAY,IAAA,CAAK;AAAA,KACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAA,GAAsB;AACpB,IAAA,IAAA,CAAK,qBAAA,EAAsB;AAC3B,IAAA,IAAI,IAAA,CAAK,KAAA,KAAU,MAAA,EAAQ,OAAO,KAAA;AAClC,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAAA,CAAU,YAAoB,MAAA,EAAuB;AACnD,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AAErB,IAAA,IAAI,IAAA,CAAK,UAAU,WAAA,EAAa;AAE9B,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,IAAA,CAAK,KAAA,EAAM;AACX,QAAA;AAAA,MACF;AAEA,MAAA,IAAA,CAAK,MAAA,EAAO;AACZ,MAAA;AAAA,IACF;AAGA,IAAA,IAAA,CAAK,aAAa,GAAG,CAAA;AAErB,IAAA,MAAM,IAAA,GAAO,cAAc,IAAA,CAAK,mBAAA;AAChC,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK,EAAE,IAAI,GAAA,EAAK,MAAA,EAAQ,MAAM,CAAA;AAE1C,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,IAAA,CAAK,mBAAA,EAAA;AACL,MAAA,IAAA,CAAK,aAAA,GAAgB,GAAA;AACrB,MAAA,IAAI,IAAA,CAAK,mBAAA,IAAuB,IAAA,CAAK,sBAAA,EAAwB;AAC3D,QAAA,IAAA,CAAK,KAAA,EAAM;AAAA,MACb;AACA,MAAA;AAAA,IACF;AAGA,IAAA,IAAA,CAAK,mBAAA,GAAsB,CAAA;AAE3B,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,IAAA,CAAK,UAAA,GAAa,GAAA;AAClB,MAAA,MAAM,SAAA,GAAY,KAAK,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAA,CAAE,MAAA;AACpD,MAAA,IAAI,SAAA,IAAa,KAAK,YAAA,EAAc;AAClC,QAAA,IAAA,CAAK,KAAA,EAAM;AAAA,MACb;AAAA,IACF;AAEA,IAAA,MAAM,SAAA,GAAY,KAAK,MAAA,CAAO,MAAA;AAC9B,IAAA,IAAI,SAAA,IAAa,KAAK,iBAAA,EAAmB;AAIvC,MAAA,IAAA,CAAK,KAAA,EAAM;AAAA,IACb;AAAA,EACF;AAAA;AAAA,EAGA,SAAA,GAAkB;AAChB,IAAA,IAAA,CAAK,KAAA,EAAM;AAAA,EACb;AAAA;AAAA,EAGA,UAAA,GAAmB;AACjB,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EACd;AAAA,EAEQ,KAAA,GAAc;AACpB,IAAA,IAAI,IAAA,CAAK,UAAU,MAAA,EAAQ;AAC3B,IAAA,IAAA,CAAK,KAAA,GAAQ,MAAA;AACb,IAAA,IAAA,CAAK,QAAA,GAAW,KAAK,GAAA,EAAI;AAAA,EAC3B;AAAA,EAEQ,MAAA,GAAe;AACrB,IAAA,IAAA,CAAK,KAAA,GAAQ,QAAA;AACb,IAAA,IAAA,CAAK,mBAAA,GAAsB,CAAA;AAC3B,IAAA,IAAA,CAAK,SAAS,EAAC;AACf,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAAA,EAClB;AAAA;AAAA,EAGQ,qBAAA,GAA8B;AACpC,IAAA,IAAI,IAAA,CAAK,KAAA,KAAU,MAAA,IAAU,IAAA,CAAK,aAAa,IAAA,EAAM;AACrD,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK,QAAA;AAClC,IAAA,IAAI,OAAA,IAAW,KAAK,UAAA,EAAY;AAC9B,MAAA,IAAA,CAAK,KAAA,GAAQ,WAAA;AACb,MAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAAA,IAClB;AAAA,EACF;AAAA,EAEQ,aAAa,GAAA,EAAmB;AACtC,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,QAAA;AAC1B,IAAA,IAAA,CAAK,MAAA,GAAS,KAAK,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAM,MAAM,CAAA;AAAA,EACxD;AACF,CAAA;;;AC9NA,SAASA,eAAiB,KAAA,EAAgC;AACxD,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,EAAW;AACzC,IAAA,MAAM,IAAI,MAAM,8BAA8B,CAAA;AAAA,EAChD;AACA,EAAA,OAAO,KAAA;AACT;AAwBA,IAAM,uBAAA,GAAoC;AAAA;AAAA,EAExC,4NAAA;AAAA;AAAA,EAEA,kCAAA;AAAA,EACA,8CAAA;AAAA;AAAA,EAEA,iJAAA;AAAA;AAAA;AAAA,EAGA;AACF,CAAA;AAMO,SAAS,cAAc,GAAA,EAAqB;AACjD,EAAA,IAAI,MAAA,GAAS,GAAA;AACb,EAAA,KAAA,MAAW,WAAW,uBAAA,EAAyB;AAC7C,IAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS,CAAC,KAAA,KAAU;AAG1C,MAAA,MAAM,EAAA,GAAK,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAC5B,MAAA,MAAM,EAAA,GAAK,KAAA,CAAM,MAAA,CAAO,IAAI,CAAA;AAC5B,MAAA,MAAM,KAAA,GAAQ,OAAO,EAAA,GAAK,GAAA,GAAM,OAAO,EAAA,GAAK,KAAA,CAAM,EAAE,CAAA,GAAI,IAAA;AACxD,MAAA,IAAI,UAAU,IAAA,EAAM;AAClB,QAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,KAAA,CAAM,QAAQA,cAAAA,CAAc,KAAK,CAAC,CAAA,GAAI,CAAC,CAAA;AACnE,QAAA,OAAO,GAAG,IAAI,CAAA,UAAA,CAAA;AAAA,MAChB;AAGA,MAAA,MAAM,UAAU,KAAA,CAAM,KAAA,CAAM,4BAA4B,CAAA,GAAI,CAAC,CAAA,IAAK,KAAA;AAClE,MAAA,OAAO,GAAG,OAAO,CAAA,aAAA,CAAA;AAAA,IACnB,CAAC,CAAA;AAAA,EACH;AACA,EAAA,OAAO,MAAA;AACT;AAeA,IAAM,gBAAA,GAAmB,GAAA;AAEzB,IAAM,sBAAN,MAA0B;AAAA,EACP,SAAA,uBAAgB,GAAA,EAA4B;AAAA,EAC5C,OAAA;AAAA,EAEjB,YAAY,aAAA,EAAsC;AAChD,IAAA,IAAA,CAAK,OAAA,GAAU,IAAI,cAAA,CAAe,aAAa,CAAA;AAAA,EACjD;AAAA,EAEA,SAAS,IAAA,EAA4C;AACnD,IAAA,IAAA,CAAK,SAAA,CAAU,IAAI,IAAA,CAAK,GAAA,EAAK,EAAE,GAAG,IAAA,EAAM,MAAA,EAAQ,KAAA,EAAO,CAAA;AAAA,EACzD;AAAA;AAAA,EAGA,WAAW,GAAA,EAAmB;AAC5B,IAAA,IAAA,CAAK,SAAA,CAAU,OAAO,GAAG,CAAA;AAAA,EAC3B;AAAA;AAAA,EAGA,IAAI,GAAA,EAAyC;AAC3C,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,GAAG,CAAA;AAAA,EAC/B;AAAA;AAAA,EAGA,IAAA,GAAyB;AACvB,IAAA,OAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,QAAQ,CAAA;AAAA,EAC3C;AAAA;AAAA,EAGA,OAAO,IAAA,EAAgC;AACrC,IAAA,OAAO,IAAA,CAAK,MAAK,CAAE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,IAAI,CAAA;AAAA,EAClD;AAAA;AAAA,EAGA,UAAU,SAAA,EAAqC;AAC7C,IAAA,OAAO,IAAA,CAAK,MAAK,CAAE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,cAAc,SAAS,CAAA;AAAA,EAC5D;AAAA;AAAA,EAGA,IAAI,WAAA,GAAsB;AACxB,IAAA,IAAI,CAAA,GAAI,CAAA;AACR,IAAA,KAAA,MAAW,CAAA,IAAK,IAAA,CAAK,SAAA,CAAU,MAAA,EAAO,EAAG;AACvC,MAAA,IAAI,CAAC,EAAE,MAAA,EAAQ,CAAA,EAAA;AAAA,IACjB;AACA,IAAA,OAAO,CAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAuB;AACrB,IAAA,OAAO;AAAA,MACL,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB,UAAA,EAAY,KAAK,SAAA,CAAU,IAAA;AAAA,MAC3B,OAAA,EAAS,IAAA,CAAK,OAAA,CAAQ,QAAA;AAAS,KACjC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,UAAA,GAAsB;AACxB,IAAA,OAAO,KAAK,OAAA,CAAQ,UAAA;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAA,GAAsB;AACpB,IAAA,OAAO,IAAA,CAAK,QAAQ,UAAA,EAAW;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAA,CAAU,YAAoB,MAAA,EAAuB;AACnD,IAAA,IAAA,CAAK,OAAA,CAAQ,SAAA,CAAU,UAAA,EAAY,MAAM,CAAA;AAAA,EAC3C;AAAA;AAAA,EAGA,gBAAA,GAAyB;AACvB,IAAA,IAAA,CAAK,QAAQ,SAAA,EAAU;AAAA,EACzB;AAAA;AAAA,EAGA,iBAAA,GAA0B;AACxB,IAAA,IAAA,CAAK,QAAQ,UAAA,EAAW;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,IAAA,CAAK,GAAA,EAAa,IAAA,GAAiB,EAAC,EAAY;AAC9C,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,GAAG,CAAA;AAChC,IAAA,IAAI,CAAC,GAAG,OAAO,KAAA;AACf,IAAA,IAAI,CAAA,CAAE,QAAQ,OAAO,IAAA;AAErB,IAAA,MAAM,EAAE,KAAA,GAAQ,KAAA,EAAO,OAAA,GAAU,kBAAiB,GAAI,IAAA;AACtD,IAAA,MAAM,KAAA,GAAW,aAAS,KAAM,OAAA;AAEhC,IAAA,IAAI,KAAA,EAAO;AAET,MAAA,IAAI;AACF,QAAA,CAAA,CAAE,KAAA,CAAM,IAAA,CAAK,KAAA,GAAQ,SAAA,GAAY,SAAS,CAAA;AAAA,MAC5C,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,CAAA,CAAE,MAAA,GAAS,IAAA;AACX,MAAA,OAAO,IAAA;AAAA,IACT;AAGA,IAAA,IAAI;AACF,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,IAAI;AACF,UAAA,OAAA,CAAQ,IAAA,CAAK,CAAC,GAAA,EAAK,SAAS,CAAA;AAAA,QAC9B,CAAA,CAAA,MAAQ;AACN,UAAA,CAAA,CAAE,KAAA,CAAM,KAAK,SAAS,CAAA;AAAA,QACxB;AAAA,MACF,CAAA,MAAO;AACL,QAAA,IAAI;AACF,UAAA,OAAA,CAAQ,IAAA,CAAK,CAAC,GAAA,EAAK,SAAS,CAAA;AAAA,QAC9B,CAAA,CAAA,MAAQ;AACN,UAAA,CAAA,CAAE,KAAA,CAAM,KAAK,SAAS,CAAA;AAAA,QACxB;AAEA,QAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAE7B,UAAA,IAAI,IAAA,CAAK,UAAU,GAAA,CAAI,GAAG,KAAK,CAAC,CAAA,CAAE,MAAM,MAAA,EAAQ;AAC9C,YAAA,IAAI;AACF,cAAA,OAAA,CAAQ,IAAA,CAAK,CAAC,GAAA,EAAK,SAAS,CAAA;AAAA,YAC9B,CAAA,CAAA,MAAQ;AACN,cAAA,IAAI;AACF,gBAAA,CAAA,CAAE,KAAA,CAAM,KAAK,SAAS,CAAA;AAAA,cACxB,CAAA,CAAA,MAAQ;AAAA,cAER;AAAA,YACF;AAAA,UACF;AAAA,QACF,GAAG,OAAO,CAAA;AACV,QAAA,KAAA,CAAM,KAAA,IAAQ;AAAA,MAChB;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AACA,IAAA,CAAA,CAAE,MAAA,GAAS,IAAA;AACX,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAA,CAAQ,IAAA,GAAiB,EAAC,EAAa;AACrC,IAAA,MAAM,OAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,MAAM,CAAA;AAC7C,IAAA,MAAM,SAAmB,EAAC;AAC1B,IAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,MAAA,IAAI,KAAK,IAAA,CAAK,GAAA,EAAK,IAAI,CAAA,EAAG,MAAA,CAAO,KAAK,GAAG,CAAA;AAAA,IAC3C;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAA,CAAY,SAAA,EAAmB,IAAA,GAAiB,EAAC,EAAa;AAC5D,IAAA,MAAM,IAAA,GAAO,KAAK,SAAA,CAAU,SAAS,EAAE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,GAAG,CAAA;AACvD,IAAA,MAAM,SAAmB,EAAC;AAC1B,IAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,MAAA,IAAI,KAAK,IAAA,CAAK,GAAA,EAAK,IAAI,CAAA,EAAG,MAAA,CAAO,KAAK,GAAG,CAAA;AAAA,IAC3C;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AACF,CAAA;AAGA,IAAI,SAAA;AAEG,SAAS,kBAAA,GAA0C;AACxD,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,SAAA,GAAY,IAAI,mBAAA,EAAoB;AAAA,EACtC;AACA,EAAA,OAAO,SAAA;AACT;;;ACnRA,IAAM,UAAA,GAAa,KAAA;AACnB,IAAM,eAAA,GAAkB,GAAA;AAIxB,IAAM,wBAAA,GAA2B,GAAA;AACjC,IAAM,qBAAqB,CAAA,GAAI,IAAA;AAExB,IAAM,QAAA,GAAwC;AAAA,EACnD,IAAA,EAAM,MAAA;AAAA,EACN,QAAA,EAAU,OAAA;AAAA,EACV,WAAA,EACE,wRAAA;AAAA,EAGF,SAAA,EACE,miBAAA;AAAA,EAQF,UAAA,EAAY,SAAA;AAAA,EACZ,QAAA,EAAU,IAAA;AAAA,EACV,QAAA,EAAU,aAAA;AAAA;AAAA;AAAA;AAAA,EAIV,UAAA,EAAY,SAAA;AAAA,EACZ,YAAA,EAAc,CAAC,iBAAiB,CAAA;AAAA,EAChC,SAAA,EAAW,GAAA;AAAA,EACX,cAAA,EAAgB,UAAA;AAAA,EAChB,mBAAA,EAAqB,GAAA;AAAA,EACrB,WAAA,EAAa;AAAA,IACX,IAAA,EAAM,QAAA;AAAA,IACN,UAAA,EAAY;AAAA,MACV,OAAA,EAAS;AAAA,QACP,IAAA,EAAM,QAAA;AAAA,QACN,WAAA,EAAa;AAAA,OACf;AAAA,MACA,UAAA,EAAY;AAAA,QACV,IAAA,EAAM,SAAA;AAAA,QACN,WAAA,EAAa;AAAA,OACf;AAAA,MACA,UAAA,EAAY;AAAA,QACV,IAAA,EAAM,SAAA;AAAA,QACN,WAAA,EAAa;AAAA;AACf,KACF;AAAA,IACA,QAAA,EAAU,CAAC,SAAS;AAAA,GACtB;AAAA,EACA,MAAM,OAAA,CAAQ,KAAA,EAAO,GAAA,EAAK,IAAA,EAAM;AAC9B,IAAA,IAAI,KAAA;AACJ,IAAA,MAAM,gBAAgB,QAAA,CAAS,aAAA;AAC/B,IAAA,IAAI,CAAC,aAAA,EAAe,MAAM,IAAI,MAAM,wCAAwC,CAAA;AAC5E,IAAA,WAAA,MAAiB,EAAA,IAAM,aAAA,CAAc,KAAA,EAAO,GAAA,EAAK,IAAI,CAAA,EAAG;AACtD,MAAA,IAAI,EAAA,CAAG,IAAA,KAAS,OAAA,EAAS,KAAA,GAAQ,EAAA,CAAG,MAAA;AAAA,IACtC;AACA,IAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,MAAM,wCAAwC,CAAA;AACpE,IAAA,OAAO,KAAA;AAAA,EACT,CAAA;AAAA,EACA,OAAO,aAAA,CAAc,KAAA,EAAO,GAAA,EAAK,IAAA,EAAmD;AAClF,IAAA,IAAI,CAAC,KAAA,EAAO,OAAA,EAAS,MAAM,IAAI,MAAM,2BAA2B,CAAA;AAEhE,IAAA,MAAM,WAAW,kBAAA,EAAmB;AACpC,IAAA,IAAI,CAAC,QAAA,CAAS,UAAA,EAAW,EAAG;AAC1B,MAAA,MAAM;AAAA,QACJ,IAAA,EAAM,OAAA;AAAA,QACN,MAAA,EAAQ;AAAA,UACN,MAAA,EAAQ,EAAA;AAAA,UACR,SAAA,EAAW,CAAA;AAAA,UACX,SAAA,EAAW,KAAA;AAAA,UACX,GAAA,EAAK,IAAA;AAAA,UACL,KAAA,EACE;AAAA;AACJ,OACF;AACA,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,IAAI,KAAA,CAAM,UAAA,IAAc,eAAA,EAAiB,GAAO,CAAC,CAAA;AAEpF,IAAA,MAAM,KAAA,GAAWC,aAAS,KAAM,OAAA;AAChC,IAAA,MAAM,KAAA,GAAQ,KAAA,GACT,OAAA,CAAQ,GAAA,CAAI,SAAS,KAAK,SAAA,GAC1B,OAAA,CAAQ,GAAA,CAAI,OAAO,CAAA,IAAK,WAAA;AAC7B,IAAA,MAAM,IAAA,GAAO,KAAA,GAAQ,CAAC,IAAA,EAAM,KAAA,CAAM,OAAO,CAAA,GAAI,CAAC,IAAA,EAAM,KAAA,CAAM,OAAO,CAAA;AAEjE,IAAA,MAAM,GAAA,GAAM,aAAA,CAAc,GAAA,CAAI,OAAA,EAAS,EAAE,CAAA;AAQzC,IAAA,MAAM,QAAA,GAAW,KAAA,GAAQ,CAAC,CAAC,MAAM,UAAA,GAAa,IAAA;AAE9C,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAE3B,IAAA,IAAI,MAAM,UAAA,EAAY;AAGpB,MAAA,IAAIC,IAAAA,GAAM,EAAA;AACV,MAAA,IAAI,SAAA,GAAY,KAAA;AAChB,MAAA,MAAMC,MAAAA,GAAQ,KAAA,CAAM,KAAA,EAAO,IAAA,EAAM;AAAA,QAC/B,KAAK,GAAA,CAAI,WAAA;AAAA,QACT,GAAA;AAAA,QACA,KAAA,EAAO,CAAC,QAAA,EAAU,MAAA,EAAQ,MAAM,CAAA;AAAA,QAChC,QAAA,EAAU,IAAA;AAAA,QACV,QAAQ,IAAA,CAAK;AAAA,OACd,CAAA;AACD,MAAA,MAAMC,OAAMD,MAAAA,CAAM,GAAA;AAClB,MAAA,IAAI,OAAOC,SAAQ,QAAA,EAAU;AAC3B,QAAA,QAAA,CAAS,QAAA,CAAS;AAAA,UAChB,GAAA,EAAAA,IAAAA;AAAA,UACA,IAAA,EAAM,MAAA;AAAA,UACN,OAAA,EAAS,aAAA,CAAc,KAAA,CAAM,OAAO,CAAA;AAAA,UACpC,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,UACpB,SAAA,EAAW,IAAI,OAAA,EAAS,EAAA;AAAA,UACxB,KAAA,EAAAD;AAAA,SACD,CAAA;AACD,QAAAA,OAAM,EAAA,CAAG,OAAA,EAAS,MAAM,QAAA,CAAS,UAAA,CAAWC,IAAG,CAAC,CAAA;AAAA,MAClD;AACA,MAAAD,MAAAA,CAAM,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,KAAA,KAAkB;AAC1C,QAAA,IAAI,CAAC,SAAA,EAAW;AACd,UAAA,MAAM,MAAA,GAAS,aAAaD,IAAAA,CAAI,MAAA;AAChC,UAAA,IAAI,SAAS,CAAA,EAAG;AACd,YAAAA,QAAO,KAAA,CAAM,QAAA,EAAS,CAAE,KAAA,CAAM,GAAG,MAAM,CAAA;AAAA,UACzC;AACA,UAAA,IAAIA,IAAAA,CAAI,MAAA,IAAU,UAAA,EAAY,SAAA,GAAY,IAAA;AAAA,QAC5C;AAAA,MACF,CAAC,CAAA;AACD,MAAAC,MAAAA,CAAM,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,KAAA,KAAkB;AAC1C,QAAA,IAAI,CAAC,SAAA,EAAW;AACd,UAAA,MAAM,MAAA,GAAS,aAAaD,IAAAA,CAAI,MAAA;AAChC,UAAA,IAAI,SAAS,CAAA,EAAG;AACd,YAAAA,QAAO,KAAA,CAAM,QAAA,EAAS,CAAE,KAAA,CAAM,GAAG,MAAM,CAAA;AAAA,UACzC;AACA,UAAA,IAAIA,IAAAA,CAAI,MAAA,IAAU,UAAA,EAAY,SAAA,GAAY,IAAA;AAAA,QAC5C;AAAA,MACF,CAAC,CAAA;AACD,MAAAC,MAAAA,CAAM,EAAA,CAAG,OAAA,EAAS,MAAM;AACtB,QAAA,QAAA,CAAS,SAAA,CAAU,IAAA,CAAK,GAAA,EAAI,GAAI,WAAW,KAAK,CAAA;AAAA,MAClD,CAAC,CAAA;AACD,MAAA,IAAI,OAAOC,IAAAA,KAAQ,QAAA,EAAUD,OAAM,KAAA,EAAM;AACzC,MAAA,MAAM;AAAA,QACJ,IAAA,EAAM,OAAA;AAAA,QACN,MAAA,EAAQ;AAAA,UACN,MAAA,EAAQ,uBAAuBD,IAAG,CAAA;AAAA,UAClC,SAAA,EAAW,IAAA;AAAA,UACX,SAAA,EAAW,KAAA;AAAA,UACX,GAAA,EAAAE;AAAA;AACF,OACF;AACA,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,EAAO,IAAA,EAAM;AAAA,MAC/B,KAAK,GAAA,CAAI,WAAA;AAAA,MACT,GAAA;AAAA,MACA,KAAA,EAAO,CAAC,QAAA,EAAU,MAAA,EAAQ,MAAM,CAAA;AAAA,MAChC,QAAA;AAAA,MACA,QAAQ,IAAA,CAAK;AAAA,KACd,CAAA;AAGD,IAAA,MAAM,MAAM,KAAA,CAAM,GAAA;AAClB,IAAA,IAAI,OAAO,QAAQ,QAAA,EAAU;AAC3B,MAAA,QAAA,CAAS,QAAA,CAAS;AAAA,QAChB,GAAA;AAAA,QACA,IAAA,EAAM,MAAA;AAAA,QACN,OAAA,EAAS,aAAA,CAAc,KAAA,CAAM,OAAO,CAAA;AAAA,QACpC,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,QACpB,SAAA,EAAW,IAAI,OAAA,EAAS,EAAA;AAAA,QACxB;AAAA,OACD,CAAA;AAAA,IACH;AAEA,IAAA,IAAI,GAAA,GAAM,EAAA;AACV,IAAA,IAAI,OAAA,GAAU,EAAA;AACd,IAAA,IAAI,QAAA,GAAW,KAAA;AACf,IAAA,MAAM,SAA2B,EAAC;AAClC,IAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,MAAA,QAAA,GAAW,IAAA;AACX,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,IAAI;AACF,UAAA,KAAA,CAAM,IAAA,EAAK;AAAA,QACb,CAAA,CAAA,MAAQ;AAAA,QAER;AAAA,MACF,CAAA,MAAO;AACL,QAAA,IAAI;AACF,UAAA,IAAI,OAAO,KAAA,CAAM,GAAA,KAAQ,QAAA,EAAU;AACjC,YAAA,IAAI;AACF,cAAA,OAAA,CAAQ,IAAA,CAAK,CAAC,KAAA,CAAM,GAAA,EAAK,SAAS,CAAA;AAAA,YACpC,CAAA,CAAA,MAAQ;AACN,cAAA,KAAA,CAAM,KAAK,SAAS,CAAA;AAAA,YACtB;AAAA,UACF,CAAA,MAAO;AACL,YAAA,KAAA,CAAM,KAAK,SAAS,CAAA;AAAA,UACtB;AACA,UAAA,MAAM,SAAA,GAAY,WAAW,MAAM;AACjC,YAAA,IAAI;AACF,cAAA,IAAI,OAAO,KAAA,CAAM,GAAA,KAAQ,QAAA,EAAU;AACjC,gBAAA,IAAI;AACF,kBAAA,OAAA,CAAQ,IAAA,CAAK,CAAC,KAAA,CAAM,GAAA,EAAK,SAAS,CAAA;AAAA,gBACpC,CAAA,CAAA,MAAQ;AACN,kBAAA,KAAA,CAAM,KAAK,SAAS,CAAA;AAAA,gBACtB;AAAA,cACF,CAAA,MAAO;AACL,gBAAA,KAAA,CAAM,KAAK,SAAS,CAAA;AAAA,cACtB;AAAA,YACF,CAAA,CAAA,MAAQ;AAAA,YAER,CAAA,SAAE;AAIA,cAAA,SAAA,CAAU,KAAA,IAAQ;AAAA,YACpB;AAAA,UACF,GAAG,GAAI,CAAA;AACP,UAAA,MAAA,CAAO,KAAK,SAAS,CAAA;AAAA,QACvB,CAAA,CAAA,MAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,GAAG,SAAS,CAAA;AACZ,IAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AACjB,IAAA,KAAA,CAAM,KAAA,IAAQ;AAOd,IAAA,MAAM,QAAiB,EAAC;AACxB,IAAA,IAAI,WAAA,GAA2C,IAAA;AAC/C,IAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAAa;AACzB,MAAA,IAAI,WAAA,EAAa;AACf,QAAA,MAAM,CAAA,GAAI,WAAA;AACV,QAAA,WAAA,GAAc,IAAA;AACd,QAAA,CAAA,CAAE,CAAC,CAAA;AAAA,MACL,CAAA,MAAO;AACL,QAAA,KAAA,CAAM,KAAK,CAAC,CAAA;AAAA,MACd;AAAA,IACF,CAAA;AACA,IAAA,MAAM,IAAA,GAAO,MACX,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY;AACvB,MAAA,MAAM,CAAA,GAAI,MAAM,KAAA,EAAM;AACtB,MAAA,IAAI,CAAA,UAAW,CAAC,CAAA;AAAA,WACX,WAAA,GAAc,OAAA;AAAA,IACrB,CAAC,CAAA;AAEH,IAAA,IAAI,SAAA,GAAY,KAAK,GAAA,EAAI;AACzB,IAAA,MAAM,QAAQ,MAAM;AAClB,MAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AACjC,MAAA,MAAM,IAAA,GAAO,OAAA;AACb,MAAA,OAAA,GAAU,EAAA;AACV,MAAA,SAAA,GAAY,KAAK,GAAA,EAAI;AACrB,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAEA,IAAA,KAAA,CAAM,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,KAAA,KAAU;AAClC,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,EAAS;AAC5B,MAAA,GAAA,IAAO,IAAA;AACP,MAAA,OAAA,IAAW,IAAA;AACX,MAAA,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,CAAA;AAAA,IAC7B,CAAC,CAAA;AACD,IAAA,KAAA,CAAM,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,KAAA,KAAU;AAClC,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,EAAS;AAC5B,MAAA,GAAA,IAAO,IAAA;AACP,MAAA,OAAA,IAAW,IAAA;AACX,MAAA,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,CAAA;AAAA,IAC7B,CAAC,CAAA;AAED,IAAA,KAAA,CAAM,EAAA,CAAG,OAAA,EAAS,CAAC,GAAA,KAAQ;AACzB,MAAA,KAAA,MAAW,CAAA,IAAK,MAAA,EAAQ,YAAA,CAAa,CAAC,CAAA;AACtC,MAAA,QAAA,CAAS,SAAA,CAAU,IAAA,CAAK,GAAA,EAAI,GAAI,WAAW,IAAI,CAAA;AAC/C,MAAA,IAAA,CAAK,EAAE,IAAA,EAAM,OAAA,EAAS,GAAA,EAAK,CAAA;AAAA,IAC7B,CAAC,CAAA;AACD,IAAA,KAAA,CAAM,EAAA,CAAG,OAAA,EAAS,CAAC,IAAA,KAAS;AAC1B,MAAA,KAAA,MAAW,CAAA,IAAK,MAAA,EAAQ,YAAA,CAAa,CAAC,CAAA;AACtC,MAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,EAAU,QAAA,CAAS,WAAW,GAAG,CAAA;AACpD,MAAA,QAAA,CAAS,SAAA,CAAU,KAAK,GAAA,EAAI,GAAI,WAAW,IAAA,KAAS,CAAA,IAAK,SAAS,IAAI,CAAA;AACtE,MAAA,IAAA,CAAK,EAAE,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,CAAA;AAAA,IAC5B,CAAC,CAAA;AAED,IAAA,IAAI;AACF,MAAA,OAAO,IAAA,EAAM;AACX,QAAA,MAAM,CAAA,GAAI,MAAM,IAAA,EAAK;AACrB,QAAA,IAAI,CAAA,CAAE,IAAA,KAAS,OAAA,EAAS,MAAM,CAAA,CAAE,GAAA;AAChC,QAAA,IAAI,CAAA,CAAE,SAAS,KAAA,EAAO;AACpB,UAAA,MAAM,YAAY,KAAA,EAAM;AACxB,UAAA,IAAI,cAAc,IAAA,EAAM;AACtB,YAAA,MAAM,EAAE,IAAA,EAAM,gBAAA,EAAkB,IAAA,EAAM,SAAA,EAAU;AAAA,UAClD;AACA,UAAA,MAAM;AAAA,YACJ,IAAA,EAAM,OAAA;AAAA,YACN,MAAA,EAAQ;AAAA,cACN,MAAA,EAAQ,uBAAuB,GAAG,CAAA;AAAA,cAClC,WAAW,CAAA,CAAE,IAAA;AAAA,cACb,SAAA,EAAW;AAAA;AACb,WACF;AACA,UAAA;AAAA,QACF;AACA,QAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,QAAA,IAAI,OAAA,CAAQ,MAAA,IAAU,kBAAA,IAAsB,GAAA,GAAM,aAAa,wBAAA,EAA0B;AACvF,UAAA,MAAM,OAAO,KAAA,EAAM;AACnB,UAAA,IAAI,IAAA,EAAM,MAAM,EAAE,IAAA,EAAM,kBAAkB,IAAA,EAAK;AAAA,QACjD;AAAA,MACF;AAAA,IACF,CAAA,SAAE;AACA,MAAA,KAAA,MAAW,CAAA,IAAK,MAAA,EAAQ,YAAA,CAAa,CAAC,CAAA;AAAA,IACxC;AAAA,EACF;AACF","file":"bash.js","sourcesContent":["import * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport * as Core from '@wrongstack/core';\nimport type { Context } from '@wrongstack/core';\n\n\n\nfunction expectDefined<T>(value: T | null | undefined): T {\n if (value === null || value === undefined) {\n throw new Error('Expected value to be defined');\n }\n return value;\n}\n\n/** Detected package manager for a project directory. */\nexport type PackageManager = 'pnpm' | 'yarn' | 'npm';\n\n/**\n * Detect the project's package manager by inspecting lockfiles in `cwd`.\n * Order: pnpm → yarn → npm (default). Missing or unreadable directories fall\n * back to `npm` rather than throwing, so a `safeResolve`-checked cwd that\n * happens to be empty never aborts the tool.\n */\nexport async function detectPackageManager(cwd: string): Promise<PackageManager> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(`${cwd}/pnpm-lock.yaml`);\n return 'pnpm';\n } catch {\n /* not pnpm */\n }\n try {\n await stat(`${cwd}/yarn.lock`);\n return 'yarn';\n } catch {\n /* not yarn */\n }\n return 'npm';\n}\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const root = path.resolve(ctx.projectRoot);\n const target = path.resolve(absPath);\n const rel = path.relative(root, target);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\n }\n return target;\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\n/**\n * Defense against in-root→out-of-root symlink escape (CWE-59). `safeResolve`\n * only does a syntactic `../` check, so a symlink that lives *inside* the\n * project root but points outside still passes it. This resolves the path\n * through `fs.realpath` and re-verifies containment against the realpath of\n * the project root (comparing like-for-like, since the root itself may be a\n * symlink — macOS `/var`→`/private/var`, Windows 8.3 short names). For a path\n * that does not exist yet (e.g. a `write` to a new file) the nearest existing\n * ancestor directory is checked instead. Throws if the real target escapes.\n *\n * Mirrors the per-file guard already used in `replace.ts`/`grep.ts`; applied\n * to single-file `read`/`edit`/`write` it throws (rather than skips) because\n * the caller named exactly one file.\n */\nexport async function assertRealInsideRoot(absPath: string, ctx: Context): Promise<void> {\n const realRoot = await fsp.realpath(ctx.projectRoot).catch(() => path.resolve(ctx.projectRoot));\n let probe = absPath;\n for (;;) {\n let real: string;\n try {\n real = await fsp.realpath(probe);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n const parent = path.dirname(probe);\n if (parent === probe) return; // reached fs root without escaping\n probe = parent;\n continue;\n }\n throw err;\n }\n const rel = path.relative(realRoot, real);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(\n `Path \"${absPath}\" resolves through a symlink outside project root \"${realRoot}\"`,\n );\n }\n return;\n }\n}\n\n/** `safeResolve` + symlink realpath containment check. Async. */\nexport async function safeResolveReal(input: string, ctx: Context): Promise<string> {\n const abs = safeResolve(input, ctx);\n await assertRealInsideRoot(abs, ctx);\n return abs;\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n// ─── Command-output normalization (token-saving) ────────────────────────────\n//\n// Raw process output is full of tokens the model gains nothing from: ANSI\n// escapes, carriage-return progress spam, runs of identical warning lines, and\n// huge tails of build noise. These helpers strip that noise before the output\n// reaches the LLM. They are scoped to COMMAND tools (bash/git/exec and the\n// _spawn-stream consumers) — never applied to structured/code outputs.\n\n/** Unified byte cap for all command tool output fed to the model. */\nexport const COMMAND_OUTPUT_MAX_BYTES = 32_768;\n\n/** Runs of >= this many identical consecutive lines are collapsed. */\nconst REPEAT_RUN_THRESHOLD = 3;\n\n/**\n * Collapse carriage-return overwrites the way a terminal would: `\\r\\n` becomes\n * `\\n`, and a bare `\\r` (progress redraw) keeps only the text after the LAST\n * `\\r` on its physical line. Without this, a single progress bar that redraws\n * 200 times explodes into 200 lines.\n */\nexport function collapseCarriageReturns(text: string): string {\n const lf = text.replace(/\\r\\n/g, '\\n');\n if (!lf.includes('\\r')) return lf;\n return lf\n .split('\\n')\n .map((line) => (line.includes('\\r') ? line.slice(line.lastIndexOf('\\r') + 1) : line))\n .join('\\n');\n}\n\n/**\n * Collapse a run of `minRun`+ identical consecutive lines into the line once\n * plus a marker. Consecutive-only — it never reorders or dedups non-adjacent\n * lines, so diffs/source stay intact.\n */\nexport function collapseConsecutiveDuplicates(text: string, minRun = REPEAT_RUN_THRESHOLD): string {\n const lines = text.split('\\n');\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n let j = i + 1;\n while (j < lines.length && lines[j] === lines[i]) j++;\n const run = j - i;\n if (run >= minRun) {\n out.push(expectDefined(lines[i]), `… ⟨repeated ${run}×⟩`);\n } else {\n for (let k = i; k < j; k++) out.push(expectDefined(lines[k]));\n }\n i = j;\n }\n return out.join('\\n');\n}\n\n/** Largest prefix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeHeadBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(0, mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(0, lo);\n}\n\n/** Largest suffix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeTailBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(s.length - mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(s.length - lo);\n}\n\n/**\n * Truncate to `maxBytes` keeping BOTH ends — the head (what ran / early context)\n * and the tail (errors and summaries usually land last), biased ~45/55 toward\n * the tail. The result never exceeds `maxBytes`.\n */\nexport function truncateHeadTail(s: string, maxBytes: number): string {\n const total = Buffer.byteLength(s, 'utf8');\n if (total <= maxBytes) return s;\n // Reserve a fixed allowance for the marker so the final string can't exceed\n // the cap even though the dropped-byte count's digit width varies.\n const MARKER_RESERVE = 64;\n const avail = Math.max(0, maxBytes - MARKER_RESERVE);\n const headBudget = Math.floor(avail * 0.45);\n const head = takeHeadBytes(s, headBudget);\n const tail = takeTailBytes(s, avail - Buffer.byteLength(head, 'utf8'));\n const kept = Buffer.byteLength(head, 'utf8') + Buffer.byteLength(tail, 'utf8');\n return `${head}\\n…[truncated ${total - kept} bytes]…\\n${tail}`;\n}\n\n/**\n * Full token-saving pipeline for command tool output: strip ANSI → collapse\n * carriage-return progress → trim trailing whitespace → collapse identical\n * consecutive lines → squeeze blank-line runs → head+tail truncate to the cap.\n */\nexport function normalizeCommandOutput(\n raw: string,\n opts: { maxBytes?: number | undefined } = {},\n): string {\n if (!raw) return raw;\n let text = Core.stripAnsi(raw);\n text = collapseCarriageReturns(text);\n text = text.replace(/[ \\t]+$/gm, ''); // trailing whitespace per line\n text = collapseConsecutiveDuplicates(text);\n text = text.replace(/\\n{3,}/g, '\\n\\n'); // >=2 blank lines → 1\n return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);\n}\n","/**\n * CircuitBreaker — prevents runaway bash/exec tool chains by:\n *\n * - Tripping on consecutive failures (models that keep repeating the\n * same failing command, e.g. `npm install` with wrong args in a loop)\n * - Tripping on slow call ratio (too many long-running commands suggest\n * a hung subprocess that the model doesn't know how to kill)\n * - Rate-limiting bursts (rapid succession of commands without reading\n * output suggests the model isn't processing results)\n * - Auto-recovering after a cooldown period so a fixed model can resume\n *\n * The breaker is owned by the ProcessRegistry so any tool that registers\n * a process participates in the same circuit. \"Per-tool\" isolation is\n * intentionally NOT implemented — the model treats bash/exec as one\n * resource pool; isolating them would let the model route around the\n * breaker by alternating which tool it uses.\n */\n\nexport interface CircuitBreakerConfig {\n /**\n * Consecutive failures before trip. Default: 5.\n * A single success resets this counter to 0.\n */\n maxConsecutiveFailures?: number | undefined;\n /**\n * Slow-call threshold in ms. A call that runs longer than this is\n * counted as \"slow\". Default: 60_000 (1 minute).\n */\n slowCallThresholdMs?: number | undefined;\n /**\n * Max slow calls before trip (within the sliding window). Default: 3.\n */\n maxSlowCalls?: number | undefined;\n /**\n * Sliding window for rate-limit and slow-call counting, in ms.\n * Default: 60_000 (1 minute).\n */\n windowMs?: number | undefined;\n /**\n * Max calls within the sliding window. Default: 30.\n * Burst exceeding this trips the breaker immediately.\n */\n maxCallsPerWindow?: number | undefined;\n /**\n * Cooldown before auto-recovery attempt, in ms. Default: 30_000 (30s).\n * After this the breaker enters \"half-open\" state and allows one call\n * through to test whether the problem is resolved.\n */\n cooldownMs?: number | undefined;\n}\n\ninterface CallRecord {\n at: number;\n /** True if the call threw or returned an is_error result. */\n failed: boolean;\n /** True if elapsed time exceeded slowCallThresholdMs. */\n slow: boolean;\n}\n\ntype BreakerState = 'closed' | 'open' | 'half-open';\n\nconst DEFAULT_MAX_CONSECUTIVE_FAILURES = 5;\nconst DEFAULT_SLOW_CALL_THRESHOLD_MS = 60_000;\nconst DEFAULT_MAX_SLOW_CALLS = 3;\nconst DEFAULT_WINDOW_MS = 60_000;\nconst DEFAULT_MAX_CALLS_PER_WINDOW = 30;\nconst DEFAULT_COOLDOWN_MS = 30_000;\n\nexport interface CircuitBreakerSnapshot {\n state: 'closed' | 'open' | 'half-open';\n consecutiveFailures: number;\n slowCallsInWindow: number;\n callsInWindow: number;\n windowMs: number;\n cooldownRemainingMs: number | null;\n lastFailureAt: number | null;\n lastSlowAt: number | null;\n}\n\nexport class CircuitBreaker {\n private readonly maxConsecutiveFailures: number;\n private readonly slowCallThresholdMs: number;\n private readonly maxSlowCalls: number;\n private readonly windowMs: number;\n private readonly maxCallsPerWindow: number;\n private readonly cooldownMs: number;\n\n private state: BreakerState = 'closed';\n private consecutiveFailures = 0;\n private window: CallRecord[] = [];\n private lastFailureAt: number | null = null;\n private lastSlowAt: number | null = null;\n /** Timestamp when the breaker was opened (for cooldown calculation). */\n private openedAt: number | null = null;\n\n constructor(config: CircuitBreakerConfig = {}) {\n this.maxConsecutiveFailures = config.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES;\n this.slowCallThresholdMs = config.slowCallThresholdMs ?? DEFAULT_SLOW_CALL_THRESHOLD_MS;\n this.maxSlowCalls = config.maxSlowCalls ?? DEFAULT_MAX_SLOW_CALLS;\n this.windowMs = config.windowMs ?? DEFAULT_WINDOW_MS;\n this.maxCallsPerWindow = config.maxCallsPerWindow ?? DEFAULT_MAX_CALLS_PER_WINDOW;\n this.cooldownMs = config.cooldownMs ?? DEFAULT_COOLDOWN_MS;\n }\n\n /**\n * Returns true if the circuit allows a new call to proceed.\n * When false, callers should abort the tool call and return a\n * circuit-breaker error instead of spawning a process.\n */\n get canProceed(): boolean {\n this._checkStateTransition();\n return this.state !== 'open';\n }\n\n /**\n * Snapshot of the current breaker state for observability (`/kill`).\n */\n snapshot(): CircuitBreakerSnapshot {\n this._checkStateTransition();\n const now = Date.now();\n let cooldownRemaining: number | null = null;\n if (this.openedAt !== null && this.state === 'open') {\n const elapsed = now - this.openedAt;\n cooldownRemaining = Math.max(0, this.cooldownMs - elapsed);\n }\n return {\n state: this.state,\n consecutiveFailures: this.consecutiveFailures,\n slowCallsInWindow: this.window.filter((c) => c.slow).length,\n callsInWindow: this.window.length,\n windowMs: this.windowMs,\n cooldownRemainingMs: cooldownRemaining,\n lastFailureAt: this.lastFailureAt,\n lastSlowAt: this.lastSlowAt,\n };\n }\n\n /**\n * Call this BEFORE spawning a bash/exec process.\n * Returns true if the call is allowed; false if the breaker is open.\n * When false, callers MUST NOT spawn a process.\n */\n beforeCall(): boolean {\n this._checkStateTransition();\n if (this.state === 'open') return false;\n return true;\n }\n\n /**\n * Call this AFTER a bash/exec process finishes (success or failure).\n * `durationMs` is the wall-clock time the process ran.\n * `failed` is true when the process returned a non-zero exit code or\n * threw an exception before spawning.\n */\n afterCall(durationMs: number, failed: boolean): void {\n const now = Date.now();\n\n if (this.state === 'half-open') {\n // First call through after cooldown — if it failed, go back to open.\n if (failed) {\n this._trip();\n return;\n }\n // Success in half-open → reset to closed.\n this._reset();\n return;\n }\n\n // Prune old records outside the sliding window.\n this._pruneWindow(now);\n\n const slow = durationMs >= this.slowCallThresholdMs;\n this.window.push({ at: now, failed, slow });\n\n if (failed) {\n this.consecutiveFailures++;\n this.lastFailureAt = now;\n if (this.consecutiveFailures >= this.maxConsecutiveFailures) {\n this._trip();\n }\n return;\n }\n\n // Success: reset consecutive failure counter.\n this.consecutiveFailures = 0;\n\n if (slow) {\n this.lastSlowAt = now;\n const slowCount = this.window.filter((c) => c.slow).length;\n if (slowCount >= this.maxSlowCalls) {\n this._trip();\n }\n }\n\n const callCount = this.window.length;\n if (callCount >= this.maxCallsPerWindow) {\n // Rate limit exceeded. This is a soft trip — we reset the window\n // and let the next call try immediately (the caller will still see\n // canProceed=false until the window drains naturally).\n this._trip();\n }\n }\n\n /** Force the breaker open. Used by /kill force and Ctrl+C. */\n forceOpen(): void {\n this._trip();\n }\n\n /** Force a reset to closed. Used by tests and /kill reset. */\n forceReset(): void {\n this._reset();\n }\n\n private _trip(): void {\n if (this.state === 'open') return; // already open\n this.state = 'open';\n this.openedAt = Date.now();\n }\n\n private _reset(): void {\n this.state = 'closed';\n this.consecutiveFailures = 0;\n this.window = [];\n this.openedAt = null;\n }\n\n /** Transition from open → half-open when cooldown elapses. */\n private _checkStateTransition(): void {\n if (this.state !== 'open' || this.openedAt === null) return;\n const elapsed = Date.now() - this.openedAt;\n if (elapsed >= this.cooldownMs) {\n this.state = 'half-open';\n this.openedAt = null;\n }\n }\n\n private _pruneWindow(now: number): void {\n const cutoff = now - this.windowMs;\n this.window = this.window.filter((c) => c.at >= cutoff);\n }\n}","/**\n * ProcessRegistry — global singleton that tracks all spawned child processes\n * from `bash` and `exec` tools. Enables:\n *\n * - Listing active processes (for TUI status bar)\n * - Killing individual processes or all processes (for Ctrl+C and /kill)\n * - Detecting runaway processes (hung, looping)\n * - Circuit breaker integration to prevent recursive/repeated failures\n *\n * Thread-safety: Node.js is single-threaded, but async callbacks can fire\n * in any order. All mutations go through synchronized Map methods.\n */\nimport type { ChildProcess } from 'node:child_process';\nimport * as os from 'node:os';\nimport { CircuitBreaker, type CircuitBreakerSnapshot, type CircuitBreakerConfig } from './circuit-breaker.js';\n\n\n\nfunction expectDefined<T>(value: T | null | undefined): T {\n if (value === null || value === undefined) {\n throw new Error('Expected value to be defined');\n }\n return value;\n}\n\nexport type { CircuitBreakerSnapshot, CircuitBreakerConfig } from './circuit-breaker.js';\n\nexport interface TrackedProcess {\n pid: number;\n name: string;\n /** Display-safe redacted command string — safe for logs, /ps, crash dumps.\n * Contains [REDACTED] in place of sensitive flag values. */\n command: string;\n startedAt: number;\n sessionId?: string | undefined;\n /** The raw ChildProcess handle. Never call .kill() directly on this —\n * use `kill()` below which handles process groups correctly on POSIX\n * and degrades gracefully on Windows. */\n child: ChildProcess;\n /** True once the process has been kill()ed but not yet exited.\n * We keep it in the registry until 'close' fires so callers can\n * distinguish \"still running\" from \"just exited\". */\n killed: boolean;\n}\n\n// Sensitive CLI flag patterns that may appear in process command lines.\n// Redacted to [REDACTED] so crash dumps /ps output cannot leak secrets.\nconst SENSITIVE_FLAG_PATTERNS: RegExp[] = [\n // --flag=value or --flag \"value\" (value captured up to next space or comma)\n /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\\s,][^\\s]*)?/gi,\n // -f \"value\" style short flags\n /(?<!\\w)-t(?:\\s+|\\s*=\\s*)[^\\s,]+/g,\n /(?<!\\w)-p(?:ssword)?(?:\\s+|\\s*=\\s*)[^\\s,]+/gi,\n // env var–style secrets: TOKEN=x, API_KEY=y, etc.\n /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\\s*[=:]\\s*[^\\s,]+/gi,\n // Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only\n // when preceded by a flag name (e.g. --github-token=EyJ...).\n /--\\w*(?:token|key|secret|password|passwd|auth|credential)\\w*[=\\s,][A-Za-z0-9+/=]{32,}/,\n];\n\n/**\n * Returns a display-safe copy of `cmd` with sensitive flag values replaced by [REDACTED].\n * The original string is unchanged; this is pure and has no side effects.\n */\nexport function redactCommand(cmd: string): string {\n let result = cmd;\n for (const pattern of SENSITIVE_FLAG_PATTERNS) {\n result = result.replace(pattern, (match) => {\n // Preserve the flag name portion; redact only the value part.\n // e.g. \"--token=sekrit_abc\" → \"--token=[REDACTED]\"\n const eq = match.indexOf('=');\n const sp = match.search(/\\s/);\n const delim = eq !== -1 ? '=' : sp !== -1 ? match[sp] : null;\n if (delim !== null) {\n const flag = match.slice(0, match.indexOf(expectDefined(delim)) + 1);\n return `${flag}[REDACTED]`;\n }\n // Nothing delimitable found; replace the whole token silently.\n // Short flags like -tVALUE are replaced entirely to avoid edge cases.\n const flagEnd = match.match(/^--?[a-zA-Z][a-zA-Z0-9_-]*/)?.[0] ?? match;\n return `${flagEnd}=**redacted**`;\n });\n }\n return result;\n}\n\ninterface KillOpts {\n /** SIGKILL instead of SIGTERM. Default: false (SIGTERM first). */\n force?: boolean | undefined;\n /** MS to wait between SIGTERM and SIGKILL on POSIX. Default: 2000. */\n graceMs?: number | undefined;\n}\n\nexport interface RegistryStats {\n activeCount: number;\n totalCount: number;\n breaker: CircuitBreakerSnapshot;\n}\n\nconst DEFAULT_GRACE_MS = 2000;\n\nclass ProcessRegistryImpl {\n private readonly processes = new Map<number, TrackedProcess>();\n private readonly breaker: CircuitBreaker;\n\n constructor(breakerConfig?: CircuitBreakerConfig) {\n this.breaker = new CircuitBreaker(breakerConfig);\n }\n\n register(info: Omit<TrackedProcess, 'killed'>): void {\n this.processes.set(info.pid, { ...info, killed: false });\n }\n\n /** Unregister a process by PID. Called on 'close' / 'exit' events. */\n unregister(pid: number): void {\n this.processes.delete(pid);\n }\n\n /** Get a single process by PID. */\n get(pid: number): TrackedProcess | undefined {\n return this.processes.get(pid);\n }\n\n /** Get all tracked processes. */\n list(): TrackedProcess[] {\n return Array.from(this.processes.values());\n }\n\n /** Get processes filtered by name (e.g. 'bash', 'exec'). */\n byName(name: string): TrackedProcess[] {\n return this.list().filter((p) => p.name === name);\n }\n\n /** Get processes filtered by session. */\n bySession(sessionId: string): TrackedProcess[] {\n return this.list().filter((p) => p.sessionId === sessionId);\n }\n\n /** Count of active (non-killed) processes. */\n get activeCount(): number {\n let n = 0;\n for (const p of this.processes.values()) {\n if (!p.killed) n++;\n }\n return n;\n }\n\n /**\n * Combined stats for observability — used by /ps and the TUI status bar.\n */\n stats(): RegistryStats {\n return {\n activeCount: this.activeCount,\n totalCount: this.processes.size,\n breaker: this.breaker.snapshot(),\n };\n }\n\n /**\n * Returns true if the circuit allows a new bash/exec call to proceed.\n * When false, callers MUST NOT spawn a process.\n */\n get canProceed(): boolean {\n return this.breaker.canProceed;\n }\n\n /**\n * Called before spawning a process. Returns true if allowed; false if\n * the circuit breaker is open.\n */\n beforeCall(): boolean {\n return this.breaker.beforeCall();\n }\n\n /**\n * Called after a process finishes. `durationMs` is wall-clock time;\n * `failed` is true for non-zero exit codes.\n */\n afterCall(durationMs: number, failed: boolean): void {\n this.breaker.afterCall(durationMs, failed);\n }\n\n /** Force-open the circuit breaker (Ctrl+C, /kill force). */\n forceBreakerOpen(): void {\n this.breaker.forceOpen();\n }\n\n /** Force-reset the circuit breaker to closed (/kill reset). */\n forceBreakerReset(): void {\n this.breaker.forceReset();\n }\n\n /** Kill a single process by PID.\n *\n * On POSIX: sends SIGTERM to the *process group* (-pid) so that\n * runaway grandchild processes (`sleep 9999 & disown`) are also killed.\n * After `graceMs` a SIGKILL is sent if the process hasn't exited.\n *\n * On Windows: `child.kill()` maps to TerminateProcess — process groups\n * are not meaningfully supported. A second `force=true` call sends\n * SIGKILL (which maps to TerminateProcess again — the distinction is\n * in the exit code, not the signal).\n *\n * Returns true if the process was found and kill was attempted.\n */\n kill(pid: number, opts: KillOpts = {}): boolean {\n const p = this.processes.get(pid);\n if (!p) return false;\n if (p.killed) return true; // already kill()ed, don't double-send\n\n const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;\n const isWin = os.platform() === 'win32';\n\n if (isWin) {\n // Windows: no process group semantics; just kill the process.\n try {\n p.child.kill(force ? 'SIGKILL' : 'SIGTERM');\n } catch {\n // Process may have already exited.\n }\n p.killed = true;\n return true;\n }\n\n // POSIX: kill the process group so grandchildren are cleaned up too.\n try {\n if (force) {\n try {\n process.kill(-pid, 'SIGKILL');\n } catch {\n p.child.kill('SIGKILL');\n }\n } else {\n try {\n process.kill(-pid, 'SIGTERM');\n } catch {\n p.child.kill('SIGTERM');\n }\n // Schedule SIGKILL as backup.\n const timer = setTimeout(() => {\n // Re-check: process may have exited on its own.\n if (this.processes.has(pid) && !p.child.killed) {\n try {\n process.kill(-pid, 'SIGKILL');\n } catch {\n try {\n p.child.kill('SIGKILL');\n } catch {\n /* already gone */\n }\n }\n }\n }, graceMs);\n timer.unref?.(); // Don't keep event loop alive.\n }\n } catch {\n // Process may have already exited.\n }\n p.killed = true;\n return true;\n }\n\n /**\n * Kill all tracked processes.\n * Returns the PIDs that were kill()ed.\n */\n killAll(opts: KillOpts = {}): number[] {\n const pids = Array.from(this.processes.keys());\n const killed: number[] = [];\n for (const pid of pids) {\n if (this.kill(pid, opts)) killed.push(pid);\n }\n return killed;\n }\n\n /**\n * Kill all processes for a specific session.\n * Returns the PIDs that were kill()ed.\n */\n killSession(sessionId: string, opts: KillOpts = {}): number[] {\n const pids = this.bySession(sessionId).map((p) => p.pid);\n const killed: number[] = [];\n for (const pid of pids) {\n if (this.kill(pid, opts)) killed.push(pid);\n }\n return killed;\n }\n}\n\n/** Module-level singleton. Initialized on first access. */\nlet _registry: ProcessRegistryImpl | undefined;\n\nexport function getProcessRegistry(): ProcessRegistryImpl {\n if (!_registry) {\n _registry = new ProcessRegistryImpl();\n }\n return _registry;\n}\n\n/** Reset for tests. */\nexport function _resetProcessRegistry(): void {\n _registry = undefined;\n}\n\n// ── Convenience re-exports ────────────────────────────────────────────────────\n\nexport type { KillOpts };","import { spawn } from 'node:child_process';\nimport * as os from 'node:os';\nimport type { Tool, ToolStreamEvent } from '@wrongstack/core';\nimport { buildChildEnv } from './_env.js';\nimport { normalizeCommandOutput } from './_util.js';\nimport { redactCommand } from './process-registry.js';\nimport { getProcessRegistry } from './process-registry.js';\n\ninterface BashInput {\n command: string;\n timeout_ms?: number | undefined;\n background?: boolean | undefined;\n}\n\ninterface BashOutput {\n output: string;\n exit_code: number | null;\n timed_out: boolean;\n pid?: number | null | undefined;\n error?: string | undefined;\n}\n\nconst MAX_OUTPUT = 32_768;\nconst DEFAULT_TIMEOUT = 30_000;\n// Flush partial_output every 200ms or when 4 KiB accumulates — whichever\n// comes first. Smaller batches make the TUI feel responsive; larger ones\n// keep EventBus traffic reasonable on chatty processes.\nconst STREAM_FLUSH_INTERVAL_MS = 200;\nconst STREAM_FLUSH_BYTES = 4 * 1024;\n\nexport const bashTool: Tool<BashInput, BashOutput> = {\n name: 'bash',\n category: 'Shell',\n description:\n 'Execute an arbitrary command in the user\\'s default shell (bash/zsh/pwsh/cmd). ' +\n 'stdout and stderr are merged into one stream. This is the most powerful and dangerous tool — ' +\n 'it gives the model full access to the developer\\'s machine. Prefer specialized tools whenever possible.',\n usageHint:\n 'SECURITY WARNING: This tool runs with the full privileges of the current user.\\n\\n' +\n 'Best practices for the model:\\n' +\n '- Strongly prefer `exec` for known safe commands (node, npm, pnpm, tsc, git, etc.).\\n' +\n '- Use bash only when you genuinely need shell features (pipes, redirection, complex one-liners).\\n' +\n '- Prefer single focused commands over huge `&&` chains.\\n' +\n '- Use `background: true` only for long-running processes (dev servers, watchers).\\n' +\n '- The working directory is the project root.\\n' +\n '- Output may be truncated in the middle for very large results.',\n permission: 'confirm',\n mutating: true,\n riskTier: 'destructive',\n // Trust rules match on the literal `command` string. Without subjectKey\n // the policy heuristic would have done the same here, but declaring it\n // explicitly removes the implicit cross-tool aliasing.\n subjectKey: 'command',\n capabilities: ['shell.arbitrary'],\n timeoutMs: 30_000,\n maxOutputBytes: MAX_OUTPUT,\n estimatedDurationMs: 3_000,\n inputSchema: {\n type: 'object',\n properties: {\n command: {\n type: 'string',\n description: 'The exact shell command to run. Prefer simple, focused commands.',\n },\n timeout_ms: {\n type: 'integer',\n description: 'Optional timeout for this specific command in milliseconds.',\n },\n background: {\n type: 'boolean',\n description: 'If true, launch the process in the background and return the PID immediately.',\n },\n },\n required: ['command'],\n },\n async execute(input, ctx, opts) {\n let final: BashOutput | undefined;\n const executeStream = bashTool.executeStream;\n if (!executeStream) throw new Error('bashTool: stream execution unavailable');\n for await (const ev of executeStream(input, ctx, opts)) {\n if (ev.type === 'final') final = ev.output;\n }\n if (!final) throw new Error('bash: stream ended without final event');\n return final;\n },\n async *executeStream(input, ctx, opts): AsyncGenerator<ToolStreamEvent<BashOutput>> {\n if (!input?.command) throw new Error('bash: command is required');\n\n const registry = getProcessRegistry();\n if (!registry.beforeCall()) {\n yield {\n type: 'final',\n output: {\n output: '',\n exit_code: 1,\n timed_out: false,\n pid: null,\n error:\n 'bash: circuit breaker open — too many consecutive failures or slow calls. Use /kill to inspect or /kill reset to recover.',\n },\n };\n return;\n }\n\n const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT, 600_000));\n\n const isWin = os.platform() === 'win32';\n const shell = isWin\n ? (process.env['COMSPEC'] ?? 'cmd.exe')\n : (process.env['SHELL'] ?? '/bin/bash');\n const args = isWin ? ['/c', input.command] : ['-c', input.command];\n\n const env = buildChildEnv(ctx.session?.id);\n\n // On POSIX we put the shell in its own process group so that timeout /\n // abort can kill the entire group with `process.kill(-pid)`. Otherwise\n // `bash -c \"sleep 9999 & disown\"` would leave the grandchild running.\n // `detached: true` is also reused for the user-facing background mode;\n // we always want detached on POSIX, only on Windows is it tied to the\n // explicit background flag.\n const detached = isWin ? !!input.background : true;\n\n const startedAt = Date.now();\n\n if (input.background) {\n // Background mode: capture stdout/stderr with bounded buffers so a\n // malicious command can't write unbounded output. Apply MAX_OUTPUT cap.\n let buf = '';\n let truncated = false;\n const child = spawn(shell, args, {\n cwd: ctx.projectRoot,\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: true,\n signal: opts.signal,\n });\n const pid = child.pid;\n if (typeof pid === 'number') {\n registry.register({\n pid,\n name: 'bash',\n command: redactCommand(input.command),\n startedAt: Date.now(),\n sessionId: ctx.session?.id,\n child,\n });\n child.on('close', () => registry.unregister(pid));\n }\n child.stdout?.on('data', (chunk: Buffer) => {\n if (!truncated) {\n const remain = MAX_OUTPUT - buf.length;\n if (remain > 0) {\n buf += chunk.toString().slice(0, remain);\n }\n if (buf.length >= MAX_OUTPUT) truncated = true;\n }\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n if (!truncated) {\n const remain = MAX_OUTPUT - buf.length;\n if (remain > 0) {\n buf += chunk.toString().slice(0, remain);\n }\n if (buf.length >= MAX_OUTPUT) truncated = true;\n }\n });\n child.on('close', () => {\n registry.afterCall(Date.now() - startedAt, false);\n });\n if (typeof pid === 'number') child.unref();\n yield {\n type: 'final',\n output: {\n output: normalizeCommandOutput(buf),\n exit_code: null,\n timed_out: false,\n pid,\n },\n };\n return;\n }\n\n // Foreground mode: pipe stdout/stderr for streaming output.\n const child = spawn(shell, args, {\n cwd: ctx.projectRoot,\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n detached,\n signal: opts.signal,\n });\n\n // Register with global registry so Ctrl+C / /kill can find and kill it.\n const pid = child.pid;\n if (typeof pid === 'number') {\n registry.register({\n pid,\n name: 'bash',\n command: redactCommand(input.command),\n startedAt: Date.now(),\n sessionId: ctx.session?.id,\n child,\n });\n }\n\n let buf = '';\n let pending = '';\n let timedOut = false;\n const timers: NodeJS.Timeout[] = [];\n const timer = setTimeout(() => {\n timedOut = true;\n if (isWin) {\n try {\n child.kill();\n } catch {\n /* ignore */\n }\n } else {\n try {\n if (typeof child.pid === 'number') {\n try {\n process.kill(-child.pid, 'SIGTERM');\n } catch {\n child.kill('SIGTERM');\n }\n } else {\n child.kill('SIGTERM');\n }\n const killTimer = setTimeout(() => {\n try {\n if (typeof child.pid === 'number') {\n try {\n process.kill(-child.pid, 'SIGKILL');\n } catch {\n child.kill('SIGKILL');\n }\n } else {\n child.kill('SIGKILL');\n }\n } catch {\n /* ignore */\n } finally {\n // Only unref after the callback fires; prevents a stray SIGKILL\n // from firing ~2s after a process that exited cleanly before the\n // timeout's SIGTERM was even sent.\n killTimer.unref?.();\n }\n }, 2000);\n timers.push(killTimer);\n } catch {\n /* ignore */\n }\n }\n }, timeoutMs);\n timers.push(timer);\n timer.unref?.();\n\n // Bridge the EventEmitter-style child to an async iterator.\n type Chunk =\n | { kind: 'data'; text: string }\n | { kind: 'end'; code: number | null }\n | { kind: 'error'; err: Error };\n const queue: Chunk[] = [];\n let resolveNext: ((c: Chunk) => void) | null = null;\n const push = (c: Chunk) => {\n if (resolveNext) {\n const r = resolveNext;\n resolveNext = null;\n r(c);\n } else {\n queue.push(c);\n }\n };\n const next = (): Promise<Chunk> =>\n new Promise((resolve) => {\n const c = queue.shift();\n if (c) resolve(c);\n else resolveNext = resolve;\n });\n\n let lastFlush = Date.now();\n const flush = () => {\n if (pending.length === 0) return null;\n const text = pending;\n pending = '';\n lastFlush = Date.now();\n return text;\n };\n\n child.stdout?.on('data', (chunk) => {\n const text = chunk.toString();\n buf += text;\n pending += text;\n push({ kind: 'data', text });\n });\n child.stderr?.on('data', (chunk) => {\n const text = chunk.toString();\n buf += text;\n pending += text;\n push({ kind: 'data', text });\n });\n\n child.on('error', (err) => {\n for (const t of timers) clearTimeout(t);\n registry.afterCall(Date.now() - startedAt, true);\n push({ kind: 'error', err });\n });\n child.on('close', (code) => {\n for (const t of timers) clearTimeout(t);\n if (typeof pid === 'number') registry.unregister(pid);\n registry.afterCall(Date.now() - startedAt, code !== 0 && code !== null);\n push({ kind: 'end', code });\n });\n\n try {\n while (true) {\n const c = await next();\n if (c.kind === 'error') throw c.err;\n if (c.kind === 'end') {\n const remainder = flush();\n if (remainder !== null) {\n yield { type: 'partial_output', text: remainder };\n }\n yield {\n type: 'final',\n output: {\n output: normalizeCommandOutput(buf),\n exit_code: c.code,\n timed_out: timedOut,\n },\n };\n return;\n }\n const now = Date.now();\n if (pending.length >= STREAM_FLUSH_BYTES || now - lastFlush >= STREAM_FLUSH_INTERVAL_MS) {\n const text = flush();\n if (text) yield { type: 'partial_output', text };\n }\n }\n } finally {\n for (const t of timers) clearTimeout(t);\n }\n },\n};\n\n// Re-export types so consumers can narrow on stream events.\nexport type { BashInput, BashOutput };"]}
1
+ {"version":3,"sources":["../src/_util.ts","../src/circuit-breaker.ts","../src/process-registry.ts","../src/bash.ts"],"names":["expectDefined","os2","buf","child","pid"],"mappings":"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","file":"bash.js","sourcesContent":["import * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport * as Core from '@wrongstack/core';\nimport type { Context } from '@wrongstack/core';\n\n\n\nfunction expectDefined<T>(value: T | null | undefined): T {\n if (value === null || value === undefined) {\n throw new Error('Expected value to be defined');\n }\n return value;\n}\n\n/** Detected package manager for a project directory. */\nexport type PackageManager = 'pnpm' | 'yarn' | 'npm';\n\n/**\n * Detect the project's package manager by inspecting lockfiles in `cwd`.\n * Order: pnpm → yarn → npm (default). Missing or unreadable directories fall\n * back to `npm` rather than throwing, so a `safeResolve`-checked cwd that\n * happens to be empty never aborts the tool.\n */\nexport async function detectPackageManager(cwd: string): Promise<PackageManager> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(`${cwd}/pnpm-lock.yaml`);\n return 'pnpm';\n } catch {\n /* not pnpm */\n }\n try {\n await stat(`${cwd}/yarn.lock`);\n return 'yarn';\n } catch {\n /* not yarn */\n }\n return 'npm';\n}\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const root = path.resolve(ctx.projectRoot);\n const target = path.resolve(absPath);\n const rel = path.relative(root, target);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\n }\n return target;\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\n/**\n * Defense against in-root→out-of-root symlink escape (CWE-59). `safeResolve`\n * only does a syntactic `../` check, so a symlink that lives *inside* the\n * project root but points outside still passes it. This resolves the path\n * through `fs.realpath` and re-verifies containment against the realpath of\n * the project root (comparing like-for-like, since the root itself may be a\n * symlink — macOS `/var`→`/private/var`, Windows 8.3 short names). For a path\n * that does not exist yet (e.g. a `write` to a new file) the nearest existing\n * ancestor directory is checked instead. Throws if the real target escapes.\n *\n * Mirrors the per-file guard already used in `replace.ts`/`grep.ts`; applied\n * to single-file `read`/`edit`/`write` it throws (rather than skips) because\n * the caller named exactly one file.\n */\nexport async function assertRealInsideRoot(absPath: string, ctx: Context): Promise<void> {\n const realRoot = await fsp.realpath(ctx.projectRoot).catch(() => path.resolve(ctx.projectRoot));\n let probe = absPath;\n for (;;) {\n let real: string;\n try {\n real = await fsp.realpath(probe);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n const parent = path.dirname(probe);\n if (parent === probe) return; // reached fs root without escaping\n probe = parent;\n continue;\n }\n throw err;\n }\n const rel = path.relative(realRoot, real);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(\n `Path \"${absPath}\" resolves through a symlink outside project root \"${realRoot}\"`,\n );\n }\n return;\n }\n}\n\n/** `safeResolve` + symlink realpath containment check. Async. */\nexport async function safeResolveReal(input: string, ctx: Context): Promise<string> {\n const abs = safeResolve(input, ctx);\n await assertRealInsideRoot(abs, ctx);\n return abs;\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n// ─── Command-output normalization (token-saving) ────────────────────────────\n//\n// Raw process output is full of tokens the model gains nothing from: ANSI\n// escapes, carriage-return progress spam, runs of identical warning lines, and\n// huge tails of build noise. These helpers strip that noise before the output\n// reaches the LLM. They are scoped to COMMAND tools (bash/git/exec and the\n// _spawn-stream consumers) — never applied to structured/code outputs.\n\n/** Unified byte cap for all command tool output fed to the model. */\nexport const COMMAND_OUTPUT_MAX_BYTES = 32_768;\n\n/** Runs of >= this many identical consecutive lines are collapsed. */\nconst REPEAT_RUN_THRESHOLD = 3;\n\n/**\n * Collapse carriage-return overwrites the way a terminal would: `\\r\\n` becomes\n * `\\n`, and a bare `\\r` (progress redraw) keeps only the text after the LAST\n * `\\r` on its physical line. Without this, a single progress bar that redraws\n * 200 times explodes into 200 lines.\n */\nexport function collapseCarriageReturns(text: string): string {\n const lf = text.replace(/\\r\\n/g, '\\n');\n if (!lf.includes('\\r')) return lf;\n return lf\n .split('\\n')\n .map((line) => (line.includes('\\r') ? line.slice(line.lastIndexOf('\\r') + 1) : line))\n .join('\\n');\n}\n\n/**\n * Collapse a run of `minRun`+ identical consecutive lines into the line once\n * plus a marker. Consecutive-only — it never reorders or dedups non-adjacent\n * lines, so diffs/source stay intact.\n */\nexport function collapseConsecutiveDuplicates(text: string, minRun = REPEAT_RUN_THRESHOLD): string {\n const lines = text.split('\\n');\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n let j = i + 1;\n while (j < lines.length && lines[j] === lines[i]) j++;\n const run = j - i;\n if (run >= minRun) {\n out.push(expectDefined(lines[i]), `… ⟨repeated ${run}×⟩`);\n } else {\n for (let k = i; k < j; k++) out.push(expectDefined(lines[k]));\n }\n i = j;\n }\n return out.join('\\n');\n}\n\n/** Largest prefix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeHeadBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(0, mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(0, lo);\n}\n\n/** Largest suffix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeTailBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(s.length - mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(s.length - lo);\n}\n\n/**\n * Truncate to `maxBytes` keeping BOTH ends — the head (what ran / early context)\n * and the tail (errors and summaries usually land last), biased ~45/55 toward\n * the tail. The result never exceeds `maxBytes`.\n */\nexport function truncateHeadTail(s: string, maxBytes: number): string {\n const total = Buffer.byteLength(s, 'utf8');\n if (total <= maxBytes) return s;\n // Reserve a fixed allowance for the marker so the final string can't exceed\n // the cap even though the dropped-byte count's digit width varies.\n const MARKER_RESERVE = 64;\n const avail = Math.max(0, maxBytes - MARKER_RESERVE);\n const headBudget = Math.floor(avail * 0.45);\n const head = takeHeadBytes(s, headBudget);\n const tail = takeTailBytes(s, avail - Buffer.byteLength(head, 'utf8'));\n const kept = Buffer.byteLength(head, 'utf8') + Buffer.byteLength(tail, 'utf8');\n return `${head}\\n…[truncated ${total - kept} bytes]…\\n${tail}`;\n}\n\n/**\n * Full token-saving pipeline for command tool output: strip ANSI → collapse\n * carriage-return progress → trim trailing whitespace → collapse identical\n * consecutive lines → squeeze blank-line runs → head+tail truncate to the cap.\n */\nexport function normalizeCommandOutput(\n raw: string,\n opts: { maxBytes?: number | undefined } = {},\n): string {\n if (!raw) return raw;\n let text = Core.stripAnsi(raw);\n text = collapseCarriageReturns(text);\n text = text.replace(/[ \\t]+$/gm, ''); // trailing whitespace per line\n text = collapseConsecutiveDuplicates(text);\n text = text.replace(/\\n{3,}/g, '\\n\\n'); // >=2 blank lines → 1\n return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);\n}\n","/**\n * CircuitBreaker — prevents runaway bash/exec tool chains by:\n *\n * - Tripping on consecutive failures (models that keep repeating the\n * same failing command, e.g. `npm install` with wrong args in a loop)\n * - Tripping on slow call ratio (too many long-running commands suggest\n * a hung subprocess that the model doesn't know how to kill)\n * - Rate-limiting bursts (rapid succession of commands without reading\n * output suggests the model isn't processing results)\n * - Auto-recovering after a cooldown period so a fixed model can resume\n *\n * The breaker is owned by the ProcessRegistry so any tool that registers\n * a process participates in the same circuit. \"Per-tool\" isolation is\n * intentionally NOT implemented — the model treats bash/exec as one\n * resource pool; isolating them would let the model route around the\n * breaker by alternating which tool it uses.\n */\n\nexport interface CircuitBreakerConfig {\n /**\n * Consecutive failures before trip. Default: 5.\n * A single success resets this counter to 0.\n */\n maxConsecutiveFailures?: number | undefined;\n /**\n * Slow-call threshold in ms. A call that runs longer than this is\n * counted as \"slow\". Default: 60_000 (1 minute).\n */\n slowCallThresholdMs?: number | undefined;\n /**\n * Max slow calls before trip (within the sliding window). Default: 3.\n */\n maxSlowCalls?: number | undefined;\n /**\n * Sliding window for rate-limit and slow-call counting, in ms.\n * Default: 60_000 (1 minute).\n */\n windowMs?: number | undefined;\n /**\n * Max calls within the sliding window. Default: 30.\n * Burst exceeding this trips the breaker immediately.\n */\n maxCallsPerWindow?: number | undefined;\n /**\n * Cooldown before auto-recovery attempt, in ms. Default: 30_000 (30s).\n * After this the breaker enters \"half-open\" state and allows one call\n * through to test whether the problem is resolved.\n */\n cooldownMs?: number | undefined;\n}\n\ninterface CallRecord {\n at: number;\n /** True if the call threw or returned an is_error result. */\n failed: boolean;\n /** True if elapsed time exceeded slowCallThresholdMs. */\n slow: boolean;\n}\n\ntype BreakerState = 'closed' | 'open' | 'half-open';\n\nconst DEFAULT_MAX_CONSECUTIVE_FAILURES = 5;\nconst DEFAULT_SLOW_CALL_THRESHOLD_MS = 60_000;\nconst DEFAULT_MAX_SLOW_CALLS = 3;\nconst DEFAULT_WINDOW_MS = 60_000;\nconst DEFAULT_MAX_CALLS_PER_WINDOW = 30;\nconst DEFAULT_COOLDOWN_MS = 30_000;\n\nexport interface CircuitBreakerSnapshot {\n state: 'closed' | 'open' | 'half-open';\n consecutiveFailures: number;\n slowCallsInWindow: number;\n callsInWindow: number;\n windowMs: number;\n cooldownRemainingMs: number | null;\n lastFailureAt: number | null;\n lastSlowAt: number | null;\n}\n\nexport class CircuitBreaker {\n private readonly maxConsecutiveFailures: number;\n private readonly slowCallThresholdMs: number;\n private readonly maxSlowCalls: number;\n private readonly windowMs: number;\n private readonly maxCallsPerWindow: number;\n private readonly cooldownMs: number;\n\n private state: BreakerState = 'closed';\n private consecutiveFailures = 0;\n private window: CallRecord[] = [];\n private lastFailureAt: number | null = null;\n private lastSlowAt: number | null = null;\n /** Timestamp when the breaker was opened (for cooldown calculation). */\n private openedAt: number | null = null;\n\n constructor(config: CircuitBreakerConfig = {}) {\n this.maxConsecutiveFailures = config.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES;\n this.slowCallThresholdMs = config.slowCallThresholdMs ?? DEFAULT_SLOW_CALL_THRESHOLD_MS;\n this.maxSlowCalls = config.maxSlowCalls ?? DEFAULT_MAX_SLOW_CALLS;\n this.windowMs = config.windowMs ?? DEFAULT_WINDOW_MS;\n this.maxCallsPerWindow = config.maxCallsPerWindow ?? DEFAULT_MAX_CALLS_PER_WINDOW;\n this.cooldownMs = config.cooldownMs ?? DEFAULT_COOLDOWN_MS;\n }\n\n /**\n * Returns true if the circuit allows a new call to proceed.\n * When false, callers should abort the tool call and return a\n * circuit-breaker error instead of spawning a process.\n */\n get canProceed(): boolean {\n this._checkStateTransition();\n return this.state !== 'open';\n }\n\n /**\n * Snapshot of the current breaker state for observability (`/kill`).\n */\n snapshot(): CircuitBreakerSnapshot {\n this._checkStateTransition();\n const now = Date.now();\n let cooldownRemaining: number | null = null;\n if (this.openedAt !== null && this.state === 'open') {\n const elapsed = now - this.openedAt;\n cooldownRemaining = Math.max(0, this.cooldownMs - elapsed);\n }\n return {\n state: this.state,\n consecutiveFailures: this.consecutiveFailures,\n slowCallsInWindow: this.window.filter((c) => c.slow).length,\n callsInWindow: this.window.length,\n windowMs: this.windowMs,\n cooldownRemainingMs: cooldownRemaining,\n lastFailureAt: this.lastFailureAt,\n lastSlowAt: this.lastSlowAt,\n };\n }\n\n /**\n * Call this BEFORE spawning a bash/exec process.\n * Returns true if the call is allowed; false if the breaker is open.\n * When false, callers MUST NOT spawn a process.\n */\n beforeCall(): boolean {\n this._checkStateTransition();\n if (this.state === 'open') return false;\n return true;\n }\n\n /**\n * Call this AFTER a bash/exec process finishes (success or failure).\n * `durationMs` is the wall-clock time the process ran.\n * `failed` is true when the process returned a non-zero exit code or\n * threw an exception before spawning.\n */\n afterCall(durationMs: number, failed: boolean): void {\n const now = Date.now();\n\n if (this.state === 'half-open') {\n // First call through after cooldown — if it failed, go back to open.\n if (failed) {\n this._trip();\n return;\n }\n // Success in half-open → reset to closed.\n this._reset();\n return;\n }\n\n // Prune old records outside the sliding window.\n this._pruneWindow(now);\n\n const slow = durationMs >= this.slowCallThresholdMs;\n this.window.push({ at: now, failed, slow });\n\n if (failed) {\n this.consecutiveFailures++;\n this.lastFailureAt = now;\n if (this.consecutiveFailures >= this.maxConsecutiveFailures) {\n this._trip();\n }\n return;\n }\n\n // Success: reset consecutive failure counter.\n this.consecutiveFailures = 0;\n\n if (slow) {\n this.lastSlowAt = now;\n const slowCount = this.window.filter((c) => c.slow).length;\n if (slowCount >= this.maxSlowCalls) {\n this._trip();\n }\n }\n\n const callCount = this.window.length;\n if (callCount >= this.maxCallsPerWindow) {\n // Rate limit exceeded. This is a soft trip — we reset the window\n // and let the next call try immediately (the caller will still see\n // canProceed=false until the window drains naturally).\n this._trip();\n }\n }\n\n /** Force the breaker open. Used by /kill force and Ctrl+C. */\n forceOpen(): void {\n this._trip();\n }\n\n /** Force a reset to closed. Used by tests and /kill reset. */\n forceReset(): void {\n this._reset();\n }\n\n private _trip(): void {\n if (this.state === 'open') return; // already open\n this.state = 'open';\n this.openedAt = Date.now();\n }\n\n private _reset(): void {\n this.state = 'closed';\n this.consecutiveFailures = 0;\n this.window = [];\n this.openedAt = null;\n }\n\n /** Transition from open → half-open when cooldown elapses. */\n private _checkStateTransition(): void {\n if (this.state !== 'open' || this.openedAt === null) return;\n const elapsed = Date.now() - this.openedAt;\n if (elapsed >= this.cooldownMs) {\n this.state = 'half-open';\n this.openedAt = null;\n }\n }\n\n private _pruneWindow(now: number): void {\n const cutoff = now - this.windowMs;\n this.window = this.window.filter((c) => c.at >= cutoff);\n }\n}","/**\n * ProcessRegistry — global singleton that tracks all spawned child processes\n * from `bash` and `exec` tools. Enables:\n *\n * - Listing active processes (for TUI status bar)\n * - Killing individual processes or all processes (for Ctrl+C and /kill)\n * - Detecting runaway processes (hung, looping)\n * - Circuit breaker integration to prevent recursive/repeated failures\n *\n * Thread-safety: Node.js is single-threaded, but async callbacks can fire\n * in any order. All mutations go through synchronized Map methods.\n */\nimport type { ChildProcess } from 'node:child_process';\nimport * as os from 'node:os';\nimport { CircuitBreaker, type CircuitBreakerSnapshot, type CircuitBreakerConfig } from './circuit-breaker.js';\n\n\n\nfunction expectDefined<T>(value: T | null | undefined): T {\n if (value === null || value === undefined) {\n throw new Error('Expected value to be defined');\n }\n return value;\n}\n\nexport type { CircuitBreakerSnapshot, CircuitBreakerConfig } from './circuit-breaker.js';\n\nexport interface TrackedProcess {\n pid: number;\n name: string;\n /** Display-safe redacted command string — safe for logs, /ps, crash dumps.\n * Contains [REDACTED] in place of sensitive flag values. */\n command: string;\n startedAt: number;\n sessionId?: string | undefined;\n /** The raw ChildProcess handle. Never call .kill() directly on this —\n * use `kill()` below which handles process groups correctly on POSIX\n * and degrades gracefully on Windows. */\n child: ChildProcess;\n /** True once the process has been kill()ed but not yet exited.\n * We keep it in the registry until 'close' fires so callers can\n * distinguish \"still running\" from \"just exited\". */\n killed: boolean;\n}\n\n// Sensitive CLI flag patterns that may appear in process command lines.\n// Redacted to [REDACTED] so crash dumps /ps output cannot leak secrets.\nconst SENSITIVE_FLAG_PATTERNS: RegExp[] = [\n // --flag=value or --flag \"value\" (value captured up to next space or comma)\n /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\\s,][^\\s]*)?/gi,\n // -f \"value\" style short flags\n /(?<!\\w)-t(?:\\s+|\\s*=\\s*)[^\\s,]+/g,\n /(?<!\\w)-p(?:ssword)?(?:\\s+|\\s*=\\s*)[^\\s,]+/gi,\n // env var–style secrets: TOKEN=x, API_KEY=y, etc.\n /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\\s*[=:]\\s*[^\\s,]+/gi,\n // Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only\n // when preceded by a flag name (e.g. --github-token=EyJ...).\n /--\\w*(?:token|key|secret|password|passwd|auth|credential)\\w*[=\\s,][A-Za-z0-9+/=]{32,}/,\n];\n\n/**\n * Returns a display-safe copy of `cmd` with sensitive flag values replaced by [REDACTED].\n * The original string is unchanged; this is pure and has no side effects.\n */\nexport function redactCommand(cmd: string): string {\n let result = cmd;\n for (const pattern of SENSITIVE_FLAG_PATTERNS) {\n result = result.replace(pattern, (match) => {\n // Preserve the flag name portion; redact only the value part.\n // e.g. \"--token=sekrit_abc\" → \"--token=[REDACTED]\"\n const eq = match.indexOf('=');\n const sp = match.search(/\\s/);\n const delim = eq !== -1 ? '=' : sp !== -1 ? match[sp] : null;\n if (delim !== null) {\n const flag = match.slice(0, match.indexOf(expectDefined(delim)) + 1);\n return `${flag}[REDACTED]`;\n }\n // Nothing delimitable found; replace the whole token silently.\n // Short flags like -tVALUE are replaced entirely to avoid edge cases.\n const flagEnd = match.match(/^--?[a-zA-Z][a-zA-Z0-9_-]*/)?.[0] ?? match;\n return `${flagEnd}=**redacted**`;\n });\n }\n return result;\n}\n\ninterface KillOpts {\n /** SIGKILL instead of SIGTERM. Default: false (SIGTERM first). */\n force?: boolean | undefined;\n /** MS to wait between SIGTERM and SIGKILL on POSIX. Default: 2000. */\n graceMs?: number | undefined;\n}\n\nexport interface RegistryStats {\n activeCount: number;\n totalCount: number;\n breaker: CircuitBreakerSnapshot;\n}\n\nconst DEFAULT_GRACE_MS = 2000;\n\nclass ProcessRegistryImpl {\n private readonly processes = new Map<number, TrackedProcess>();\n private readonly breaker: CircuitBreaker;\n\n constructor(breakerConfig?: CircuitBreakerConfig) {\n this.breaker = new CircuitBreaker(breakerConfig);\n }\n\n register(info: Omit<TrackedProcess, 'killed'>): void {\n this.processes.set(info.pid, { ...info, killed: false });\n }\n\n /** Unregister a process by PID. Called on 'close' / 'exit' events. */\n unregister(pid: number): void {\n this.processes.delete(pid);\n }\n\n /** Get a single process by PID. */\n get(pid: number): TrackedProcess | undefined {\n return this.processes.get(pid);\n }\n\n /** Get all tracked processes. */\n list(): TrackedProcess[] {\n return Array.from(this.processes.values());\n }\n\n /** Get processes filtered by name (e.g. 'bash', 'exec'). */\n byName(name: string): TrackedProcess[] {\n return this.list().filter((p) => p.name === name);\n }\n\n /** Get processes filtered by session. */\n bySession(sessionId: string): TrackedProcess[] {\n return this.list().filter((p) => p.sessionId === sessionId);\n }\n\n /** Count of active (non-killed) processes. */\n get activeCount(): number {\n let n = 0;\n for (const p of this.processes.values()) {\n if (!p.killed) n++;\n }\n return n;\n }\n\n /**\n * Combined stats for observability — used by /ps and the TUI status bar.\n */\n stats(): RegistryStats {\n return {\n activeCount: this.activeCount,\n totalCount: this.processes.size,\n breaker: this.breaker.snapshot(),\n };\n }\n\n /**\n * Returns true if the circuit allows a new bash/exec call to proceed.\n * When false, callers MUST NOT spawn a process.\n */\n get canProceed(): boolean {\n return this.breaker.canProceed;\n }\n\n /**\n * Called before spawning a process. Returns true if allowed; false if\n * the circuit breaker is open.\n */\n beforeCall(): boolean {\n return this.breaker.beforeCall();\n }\n\n /**\n * Called after a process finishes. `durationMs` is wall-clock time;\n * `failed` is true for non-zero exit codes.\n */\n afterCall(durationMs: number, failed: boolean): void {\n this.breaker.afterCall(durationMs, failed);\n }\n\n /** Force-open the circuit breaker (Ctrl+C, /kill force). */\n forceBreakerOpen(): void {\n this.breaker.forceOpen();\n }\n\n /** Force-reset the circuit breaker to closed (/kill reset). */\n forceBreakerReset(): void {\n this.breaker.forceReset();\n }\n\n /** Kill a single process by PID.\n *\n * On POSIX: sends SIGTERM to the *process group* (-pid) so that\n * runaway grandchild processes (`sleep 9999 & disown`) are also killed.\n * After `graceMs` a SIGKILL is sent if the process hasn't exited.\n *\n * On Windows: `child.kill()` maps to TerminateProcess — process groups\n * are not meaningfully supported. A second `force=true` call sends\n * SIGKILL (which maps to TerminateProcess again — the distinction is\n * in the exit code, not the signal).\n *\n * Returns true if the process was found and kill was attempted.\n */\n kill(pid: number, opts: KillOpts = {}): boolean {\n const p = this.processes.get(pid);\n if (!p) return false;\n if (p.killed) return true; // already kill()ed, don't double-send\n\n const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;\n const isWin = os.platform() === 'win32';\n\n if (isWin) {\n // Windows: no process group semantics; just kill the process.\n try {\n p.child.kill(force ? 'SIGKILL' : 'SIGTERM');\n } catch {\n // Process may have already exited.\n }\n p.killed = true;\n return true;\n }\n\n // POSIX: kill the process group so grandchildren are cleaned up too.\n try {\n if (force) {\n try {\n process.kill(-pid, 'SIGKILL');\n } catch {\n p.child.kill('SIGKILL');\n }\n } else {\n try {\n process.kill(-pid, 'SIGTERM');\n } catch {\n p.child.kill('SIGTERM');\n }\n // Schedule SIGKILL as backup.\n const timer = setTimeout(() => {\n // Re-check: process may have exited on its own.\n if (this.processes.has(pid) && !p.child.killed) {\n try {\n process.kill(-pid, 'SIGKILL');\n } catch {\n try {\n p.child.kill('SIGKILL');\n } catch {\n /* already gone */\n }\n }\n }\n }, graceMs);\n timer.unref?.(); // Don't keep event loop alive.\n }\n } catch {\n // Process may have already exited.\n }\n p.killed = true;\n return true;\n }\n\n /**\n * Kill all tracked processes.\n * Returns the PIDs that were kill()ed.\n */\n killAll(opts: KillOpts = {}): number[] {\n const pids = Array.from(this.processes.keys());\n const killed: number[] = [];\n for (const pid of pids) {\n if (this.kill(pid, opts)) killed.push(pid);\n }\n return killed;\n }\n\n /**\n * Kill all processes for a specific session.\n * Returns the PIDs that were kill()ed.\n */\n killSession(sessionId: string, opts: KillOpts = {}): number[] {\n const pids = this.bySession(sessionId).map((p) => p.pid);\n const killed: number[] = [];\n for (const pid of pids) {\n if (this.kill(pid, opts)) killed.push(pid);\n }\n return killed;\n }\n}\n\n/** Module-level singleton. Initialized on first access. */\nlet _registry: ProcessRegistryImpl | undefined;\n\nexport function getProcessRegistry(): ProcessRegistryImpl {\n if (!_registry) {\n _registry = new ProcessRegistryImpl();\n }\n return _registry;\n}\n\n/** Reset for tests. */\nexport function _resetProcessRegistry(): void {\n _registry = undefined;\n}\n\n// ── Convenience re-exports ────────────────────────────────────────────────────\n\nexport type { KillOpts };","import { spawn } from 'node:child_process';\nimport * as os from 'node:os';\nimport type { Tool, ToolStreamEvent } from '@wrongstack/core';\nimport { buildChildEnv } from './_env.js';\nimport { normalizeCommandOutput } from './_util.js';\nimport { redactCommand } from './process-registry.js';\nimport { getProcessRegistry } from './process-registry.js';\n\ninterface BashInput {\n command: string;\n timeout_ms?: number | undefined;\n background?: boolean | undefined;\n}\n\ninterface BashOutput {\n output: string;\n exit_code: number | null;\n timed_out: boolean;\n pid?: number | null | undefined;\n error?: string | undefined;\n}\n\nconst MAX_OUTPUT = 32_768;\n// 32 KB — keeps context manageable for arbitrary commands. bash output\n// is typically unbounded LLM tool-use context; larger caps risk pushing\n// the context window to compaction on every invocation.\nconst DEFAULT_TIMEOUT_MS = 30_000;\n// Flush partial_output every 200ms or when 4 KiB accumulates — whichever\n// comes first. Smaller batches make the TUI feel responsive; larger ones\n// keep EventBus traffic reasonable on chatty processes.\nconst STREAM_FLUSH_INTERVAL_MS = 200;\nconst STREAM_FLUSH_BYTES = 4 * 1024;\n\nexport const bashTool: Tool<BashInput, BashOutput> = {\n name: 'bash',\n category: 'Shell',\n description:\n 'Execute an arbitrary command in the user\\'s default shell (bash/zsh/pwsh/cmd). ' +\n 'stdout and stderr are merged into one stream. This is the most powerful and dangerous tool — ' +\n 'it gives the model full access to the developer\\'s machine. Prefer specialized tools whenever possible.',\n usageHint:\n 'SECURITY WARNING: This tool runs with the full privileges of the current user.\\n\\n' +\n 'Best practices for the model:\\n' +\n '- Strongly prefer `exec` for known safe commands (node, npm, pnpm, tsc, git, etc.).\\n' +\n '- Use bash only when you genuinely need shell features (pipes, redirection, complex one-liners).\\n' +\n '- Prefer single focused commands over huge `&&` chains.\\n' +\n '- Use `background: true` only for long-running processes (dev servers, watchers).\\n' +\n '- The working directory is the project root.\\n' +\n '- Output may be truncated in the middle for very large results.',\n permission: 'confirm',\n mutating: true,\n riskTier: 'destructive',\n // Trust rules match on the literal `command` string. Without subjectKey\n // the policy heuristic would have done the same here, but declaring it\n // explicitly removes the implicit cross-tool aliasing.\n subjectKey: 'command',\n capabilities: ['shell.arbitrary'],\n timeoutMs: 30_000,\n maxOutputBytes: MAX_OUTPUT,\n estimatedDurationMs: 3_000,\n inputSchema: {\n type: 'object',\n properties: {\n command: {\n type: 'string',\n description: 'The exact shell command to run. Prefer simple, focused commands.',\n },\n timeout_ms: {\n type: 'integer',\n description: 'Optional timeout for this specific command in milliseconds.',\n },\n background: {\n type: 'boolean',\n description: 'If true, launch the process in the background and return the PID immediately.',\n },\n },\n required: ['command'],\n },\n async execute(input, ctx, opts) {\n let final: BashOutput | undefined;\n const executeStream = bashTool.executeStream;\n if (!executeStream) throw new Error('bashTool: stream execution unavailable');\n for await (const ev of executeStream(input, ctx, opts)) {\n if (ev.type === 'final') final = ev.output;\n }\n if (!final) throw new Error('bash: stream ended without final event');\n return final;\n },\n async *executeStream(input, ctx, opts): AsyncGenerator<ToolStreamEvent<BashOutput>> {\n if (!input?.command) throw new Error('bash: command is required');\n\n const registry = getProcessRegistry();\n if (!registry.beforeCall()) {\n yield {\n type: 'final',\n output: {\n output: '',\n exit_code: 1,\n timed_out: false,\n pid: null,\n error:\n 'bash: circuit breaker open — too many consecutive failures or slow calls. Use /kill to inspect or /kill reset to recover.',\n },\n };\n return;\n }\n\n const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS, 600_000));\n\n const isWin = os.platform() === 'win32';\n // Use WRONGSTACK_SHELL / WRONGSTACK_COMSPEC for explicit override.\n // If not set, fall back to an allowlist: /bin/bash, /bin/zsh, /bin/sh\n // on POSIX; cmd.exe, powershell.exe on Windows. The standard SHELL and\n // COMSPEC env vars are NOT trusted — they are user-controllable and could\n // point to an arbitrary binary on shared systems.\n const shell = (() => {\n const explicit = process.env[isWin ? 'WRONGSTACK_COMSPEC' : 'WRONGSTACK_SHELL'];\n if (explicit) return explicit;\n if (isWin) return process.env['COMSPEC'] ?? 'cmd.exe';\n // POSIX: use SHELL only if it appears in a short allowlist.\n const fromEnv = process.env['SHELL'];\n if (fromEnv) {\n const name = fromEnv.split('/').pop() ?? '';\n if (['bash', 'zsh', 'sh', 'dash', 'fish'].includes(name)) return fromEnv;\n }\n return '/bin/bash';\n })();\n const args = isWin ? ['/c', input.command] : ['-c', input.command];\n\n const env = buildChildEnv(ctx.session?.id);\n\n // On POSIX we put the shell in its own process group so that timeout /\n // abort can kill the entire group with `process.kill(-pid)`. Otherwise\n // `bash -c \"sleep 9999 & disown\"` would leave the grandchild running.\n // `detached: true` is also reused for the user-facing background mode;\n // we always want detached on POSIX, only on Windows is it tied to the\n // explicit background flag.\n const detached = isWin ? !!input.background : true;\n\n const startedAt = Date.now();\n\n if (input.background) {\n // Background mode: capture stdout/stderr with bounded buffers so a\n // malicious command can't write unbounded output. Apply MAX_OUTPUT cap.\n let buf = '';\n let truncated = false;\n const child = spawn(shell, args, {\n cwd: ctx.projectRoot,\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: true,\n signal: opts.signal,\n });\n const pid = child.pid;\n if (typeof pid === 'number') {\n registry.register({\n pid,\n name: 'bash',\n command: redactCommand(input.command),\n startedAt: Date.now(),\n sessionId: ctx.session?.id,\n child,\n });\n // Register the close handler on the same tick as spawn() so the\n // handler is guaranteed to be in place before Node's event loop\n // can deliver the close event.\n child.on('close', () => registry.unregister(pid));\n }\n child.stdout?.on('data', (chunk: Buffer) => {\n if (!truncated) {\n const remain = MAX_OUTPUT - buf.length;\n if (remain > 0) {\n buf += chunk.toString().slice(0, remain);\n }\n if (buf.length >= MAX_OUTPUT) truncated = true;\n }\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n if (!truncated) {\n const remain = MAX_OUTPUT - buf.length;\n if (remain > 0) {\n buf += chunk.toString().slice(0, remain);\n }\n if (buf.length >= MAX_OUTPUT) truncated = true;\n }\n });\n child.on('close', () => {\n registry.afterCall(Date.now() - startedAt, false);\n });\n if (typeof pid === 'number') child.unref(); // unref() so the event loop can exit while this background process runs.\n yield {\n type: 'final',\n output: {\n output: normalizeCommandOutput(buf),\n exit_code: null,\n timed_out: false,\n pid,\n },\n };\n return;\n }\n\n // Foreground mode: pipe stdout/stderr for streaming output.\n const child = spawn(shell, args, {\n cwd: ctx.projectRoot,\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n detached,\n signal: opts.signal,\n });\n\n // Register with global registry so Ctrl+C / /kill can find and kill it.\n const pid = child.pid;\n if (typeof pid === 'number') {\n registry.register({\n pid,\n name: 'bash',\n command: redactCommand(input.command),\n startedAt: Date.now(),\n sessionId: ctx.session?.id,\n child,\n });\n }\n\n let buf = '';\n let pending = '';\n let timedOut = false;\n const timers: NodeJS.Timeout[] = [];\n const timer = setTimeout(() => {\n timedOut = true;\n if (isWin) {\n try {\n child.kill();\n } catch {\n /* ignore */\n }\n } else {\n try {\n if (typeof child.pid === 'number') {\n try {\n process.kill(-child.pid, 'SIGTERM');\n } catch {\n child.kill('SIGTERM');\n }\n } else {\n child.kill('SIGTERM');\n }\n const killTimer = setTimeout(() => {\n try {\n if (typeof child.pid === 'number') {\n try {\n process.kill(-child.pid, 'SIGKILL');\n } catch {\n child.kill('SIGKILL');\n }\n } else {\n child.kill('SIGKILL');\n }\n } catch {\n /* ignore */\n } finally {\n // Only unref after the callback fires; prevents a stray SIGKILL\n // from firing ~2s after a process that exited cleanly before the\n // timeout's SIGTERM was even sent.\n killTimer.unref?.();\n }\n }, 2000);\n timers.push(killTimer);\n } catch {\n /* ignore */\n }\n }\n }, timeoutMs);\n timers.push(timer);\n timer.unref?.();\n\n // Bridge the EventEmitter-style child to an async iterator.\n type Chunk =\n | { kind: 'data'; text: string }\n | { kind: 'end'; code: number | null }\n | { kind: 'error'; err: Error };\n const queue: Chunk[] = [];\n let resolveNext: ((c: Chunk) => void) | null = null;\n const push = (c: Chunk) => {\n if (resolveNext) {\n const r = resolveNext;\n resolveNext = null;\n r(c);\n } else {\n queue.push(c);\n }\n };\n const next = (): Promise<Chunk> =>\n new Promise((resolve) => {\n const c = queue.shift();\n if (c) resolve(c);\n else resolveNext = resolve;\n });\n\n let lastFlush = Date.now();\n const flush = () => {\n if (pending.length === 0) return null;\n const text = pending;\n pending = '';\n lastFlush = Date.now();\n return text;\n };\n\n child.stdout?.on('data', (chunk) => {\n const text = chunk.toString();\n buf += text;\n pending += text;\n push({ kind: 'data', text });\n });\n child.stderr?.on('data', (chunk) => {\n const text = chunk.toString();\n buf += text;\n pending += text;\n push({ kind: 'data', text });\n });\n\n child.on('error', (err) => {\n for (const t of timers) clearTimeout(t);\n registry.afterCall(Date.now() - startedAt, true);\n push({ kind: 'error', err });\n });\n child.on('close', (code) => {\n for (const t of timers) clearTimeout(t);\n if (typeof pid === 'number') registry.unregister(pid);\n registry.afterCall(Date.now() - startedAt, code !== 0 && code !== null);\n push({ kind: 'end', code });\n });\n\n try {\n while (true) {\n const c = await next();\n if (c.kind === 'error') throw c.err;\n if (c.kind === 'end') {\n const remainder = flush();\n if (remainder !== null) {\n yield { type: 'partial_output', text: remainder };\n }\n yield {\n type: 'final',\n output: {\n output: normalizeCommandOutput(buf),\n exit_code: c.code,\n timed_out: timedOut,\n },\n };\n return;\n }\n const now = Date.now();\n if (pending.length >= STREAM_FLUSH_BYTES || now - lastFlush >= STREAM_FLUSH_INTERVAL_MS) {\n const text = flush();\n if (text) yield { type: 'partial_output', text };\n }\n }\n } finally {\n for (const t of timers) clearTimeout(t);\n }\n },\n};\n\n// Re-export types so consumers can narrow on stream events.\nexport type { BashInput, BashOutput };"]}
package/dist/builtin.js CHANGED
@@ -702,7 +702,7 @@ function getProcessRegistry() {
702
702
 
703
703
  // src/bash.ts
704
704
  var MAX_OUTPUT = 32768;
705
- var DEFAULT_TIMEOUT = 3e4;
705
+ var DEFAULT_TIMEOUT_MS = 3e4;
706
706
  var STREAM_FLUSH_INTERVAL_MS = 200;
707
707
  var STREAM_FLUSH_BYTES = 4 * 1024;
708
708
  var bashTool = {
@@ -765,9 +765,19 @@ var bashTool = {
765
765
  };
766
766
  return;
767
767
  }
768
- const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT, 6e5));
768
+ const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS, 6e5));
769
769
  const isWin = os.platform() === "win32";
770
- const shell = isWin ? process.env["COMSPEC"] ?? "cmd.exe" : process.env["SHELL"] ?? "/bin/bash";
770
+ const shell = (() => {
771
+ const explicit = process.env[isWin ? "WRONGSTACK_COMSPEC" : "WRONGSTACK_SHELL"];
772
+ if (explicit) return explicit;
773
+ if (isWin) return process.env["COMSPEC"] ?? "cmd.exe";
774
+ const fromEnv = process.env["SHELL"];
775
+ if (fromEnv) {
776
+ const name = fromEnv.split("/").pop() ?? "";
777
+ if (["bash", "zsh", "sh", "dash", "fish"].includes(name)) return fromEnv;
778
+ }
779
+ return "/bin/bash";
780
+ })();
771
781
  const args = isWin ? ["/c", input.command] : ["-c", input.command];
772
782
  const env = buildChildEnv(ctx.session?.id);
773
783
  const detached = isWin ? !!input.background : true;
@@ -3717,7 +3727,7 @@ var ALLOWED_COMMANDS = {
3717
3727
  };
3718
3728
  var MAX_ARGS = 20;
3719
3729
  var MAX_OUTPUT2 = 2e5;
3720
- var TIMEOUT_MS = 3e4;
3730
+ var DEFAULT_TIMEOUT_MS2 = 3e4;
3721
3731
  var BLOCKED_ARG_PATTERNS = {
3722
3732
  // python -c/--command executes arbitrary code; python -m runs modules
3723
3733
  python: [/-c$/, /^--command$/, /^-m$/, /^--module$/],
@@ -3780,7 +3790,7 @@ var execTool = {
3780
3790
  permission: "confirm",
3781
3791
  mutating: true,
3782
3792
  riskTier: "standard",
3783
- timeoutMs: TIMEOUT_MS,
3793
+ timeoutMs: DEFAULT_TIMEOUT_MS2,
3784
3794
  capabilities: ["shell.restricted"],
3785
3795
  inputSchema: {
3786
3796
  type: "object",
@@ -3841,7 +3851,7 @@ var execTool = {
3841
3851
  };
3842
3852
  }
3843
3853
  const args = (input.args ?? []).slice(0, MAX_ARGS);
3844
- const timeout = Math.max(1, Math.min(input.timeout ?? TIMEOUT_MS, TIMEOUT_MS));
3854
+ const timeout = Math.max(1, Math.min(input.timeout ?? DEFAULT_TIMEOUT_MS2, DEFAULT_TIMEOUT_MS2));
3845
3855
  const argError = validateArgs(cmd, args);
3846
3856
  if (argError) {
3847
3857
  return {
@@ -3934,8 +3944,21 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
3934
3944
  });
3935
3945
  }
3936
3946
  var MAX_BYTES = 131072;
3937
- var TIMEOUT_MS2 = 2e4;
3947
+ var TIMEOUT_MS = 2e4;
3938
3948
  var ALLOW_PRIVATE = process.env["WRONGSTACK_FETCH_ALLOW_PRIVATE"] === "1";
3949
+ function combineSignals(signals) {
3950
+ const anyFn = AbortSignal.any;
3951
+ if (typeof anyFn === "function") return anyFn(signals);
3952
+ const ctrl = new AbortController();
3953
+ for (const sig of signals) {
3954
+ if (sig.aborted) {
3955
+ ctrl.abort(sig.reason);
3956
+ return ctrl.signal;
3957
+ }
3958
+ sig.addEventListener("abort", () => ctrl.abort(sig.reason), { once: true });
3959
+ }
3960
+ return ctrl.signal;
3961
+ }
3939
3962
  function guardedLookup(hostname, options, callback) {
3940
3963
  dns.lookup(hostname, { all: true }).then((records) => {
3941
3964
  const family = options?.family;
@@ -3978,6 +4001,10 @@ function getPinnedDispatcher() {
3978
4001
  }
3979
4002
  return pinnedAgent;
3980
4003
  }
4004
+ process.on("beforeExit", () => {
4005
+ pinnedAgent?.destroy();
4006
+ pinnedAgent = void 0;
4007
+ });
3981
4008
  async function guardedFetch(url, maxRedirects, signal, headers = {
3982
4009
  "user-agent": "WrongStack/1.0 (+https://wrongstack.com)",
3983
4010
  accept: "text/html,application/json;q=0.9,text/plain;q=0.8,*/*;q=0.1"
@@ -4027,7 +4054,7 @@ var fetchTool = {
4027
4054
  // matching that pattern on any other tool that happens to have a `url`
4028
4055
  // input field.
4029
4056
  subjectKey: "url",
4030
- timeoutMs: TIMEOUT_MS2,
4057
+ timeoutMs: TIMEOUT_MS,
4031
4058
  maxOutputBytes: MAX_BYTES,
4032
4059
  inputSchema: {
4033
4060
  type: "object",
@@ -4066,8 +4093,8 @@ var fetchTool = {
4066
4093
  await assertNotPrivate(u.hostname);
4067
4094
  yield { type: "log", text: `GET ${input.url}` };
4068
4095
  const ctrl = new AbortController();
4069
- const timer = setTimeout(() => ctrl.abort(new Error("fetch timeout")), TIMEOUT_MS2);
4070
- const combined = combineSignals(opts.signal, ctrl.signal);
4096
+ const timer = setTimeout(() => ctrl.abort(new Error("fetch timeout")), TIMEOUT_MS);
4097
+ const combined = combineSignals([opts.signal, ctrl.signal]);
4071
4098
  try {
4072
4099
  const res = await guardedFetch(input.url, 5, combined);
4073
4100
  const ct = res.headers.get("content-type") ?? "application/octet-stream";
@@ -4215,33 +4242,6 @@ function expandIPv6(addr) {
4215
4242
  if (fill < 0) return null;
4216
4243
  return [...head, ...new Array(fill).fill(0), ...tail];
4217
4244
  }
4218
- function combineSignals(...sigs) {
4219
- const anyFn = AbortSignal.any;
4220
- if (typeof anyFn === "function") {
4221
- return anyFn(sigs);
4222
- }
4223
- const ctrl = new AbortController();
4224
- const cleanups = [];
4225
- const detach = () => {
4226
- for (const fn of cleanups) fn();
4227
- cleanups.length = 0;
4228
- };
4229
- for (const s of sigs) {
4230
- if (s.aborted) {
4231
- detach();
4232
- ctrl.abort(s.reason);
4233
- return ctrl.signal;
4234
- }
4235
- const onAbort = () => {
4236
- detach();
4237
- ctrl.abort(s.reason);
4238
- };
4239
- s.addEventListener("abort", onAbort, { once: true });
4240
- cleanups.push(() => s.removeEventListener("abort", onAbort));
4241
- }
4242
- ctrl.signal.addEventListener("abort", detach, { once: true });
4243
- return ctrl.signal;
4244
- }
4245
4245
  function prettyJson(s) {
4246
4246
  try {
4247
4247
  return JSON.stringify(JSON.parse(s), null, 2);
@@ -4377,7 +4377,7 @@ async function detectFixer(cwd) {
4377
4377
  }
4378
4378
  }
4379
4379
  }
4380
- var TIMEOUT_MS3 = 3e4;
4380
+ var TIMEOUT_MS2 = 3e4;
4381
4381
  var MAX_OUTPUT3 = 1e5;
4382
4382
  var gitTool = {
4383
4383
  name: "git",
@@ -4390,7 +4390,7 @@ var gitTool = {
4390
4390
  // and `MUTATING_SUBCOMMANDS` is consulted at runtime for per-call checks.
4391
4391
  mutating: true,
4392
4392
  capabilities: ["fs.write", "shell.restricted"],
4393
- timeoutMs: TIMEOUT_MS3,
4393
+ timeoutMs: TIMEOUT_MS2,
4394
4394
  inputSchema: {
4395
4395
  type: "object",
4396
4396
  properties: {
@@ -6409,7 +6409,7 @@ function expectDefined12(value) {
6409
6409
  }
6410
6410
  var DEFAULT_NUM = 10;
6411
6411
  var MAX_RESULTS = 50;
6412
- var TIMEOUT_MS4 = 15e3;
6412
+ var TIMEOUT_MS3 = 15e3;
6413
6413
  var searchTool = {
6414
6414
  name: "search",
6415
6415
  category: "Search",
@@ -6418,7 +6418,7 @@ var searchTool = {
6418
6418
  permission: "confirm",
6419
6419
  mutating: false,
6420
6420
  capabilities: ["net.outbound"],
6421
- timeoutMs: TIMEOUT_MS4,
6421
+ timeoutMs: TIMEOUT_MS3,
6422
6422
  inputSchema: {
6423
6423
  type: "object",
6424
6424
  properties: {
@@ -6481,7 +6481,7 @@ var searchTool = {
6481
6481
  async function duckduckgoSearch(query2, num, signal) {
6482
6482
  const encoded = encodeURIComponent(query2);
6483
6483
  const url = `https://lite.duckduckgo.com/lite/?q=${encoded}&kd=-1&kl=wt-wt`;
6484
- const results = await fetchWithTimeout(url, signal, TIMEOUT_MS4).then((r) => r.text()).then((html) => parseDuckDuckGo(html, num)).catch(() => [{ title: "Search unavailable", url: "", snippet: "Could not reach DuckDuckGo" }]);
6484
+ const results = await fetchWithTimeout(url, signal, TIMEOUT_MS3).then((r) => r.text()).then((html) => parseDuckDuckGo(html, num)).catch(() => [{ title: "Search unavailable", url: "", snippet: "Could not reach DuckDuckGo" }]);
6485
6485
  return {
6486
6486
  query: query2,
6487
6487
  results,
@@ -6522,7 +6522,7 @@ function parseDuckDuckGo(html, num) {
6522
6522
  async function googleSearch(query2, num, signal) {
6523
6523
  const encoded = encodeURIComponent(query2);
6524
6524
  const url = `https://www.google.com/search?q=${encoded}&hl=en`;
6525
- const html = await fetchWithTimeout(url, signal, TIMEOUT_MS4).then((r) => r.text()).catch(() => "");
6525
+ const html = await fetchWithTimeout(url, signal, TIMEOUT_MS3).then((r) => r.text()).catch(() => "");
6526
6526
  const results = parseGoogleResults(html, num);
6527
6527
  return {
6528
6528
  query: query2,
@@ -6560,7 +6560,7 @@ function parseGoogleResults(html, num) {
6560
6560
  async function bingSearch(query2, num, signal) {
6561
6561
  const encoded = encodeURIComponent(query2);
6562
6562
  const url = `https://www.bing.com/search?q=${encoded}`;
6563
- const html = await fetchWithTimeout(url, signal, TIMEOUT_MS4).then((r) => r.text()).catch(() => "");
6563
+ const html = await fetchWithTimeout(url, signal, TIMEOUT_MS3).then((r) => r.text()).catch(() => "");
6564
6564
  const results = parseBingResults(html, num);
6565
6565
  return {
6566
6566
  query: query2,