oh-my-claude-sisyphus 3.7.10 → 3.7.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/analyst.md +1 -1
  4. package/agents/architect-low.md +1 -1
  5. package/agents/architect-medium.md +1 -1
  6. package/agents/architect.md +1 -1
  7. package/agents/build-fixer-low.md +0 -1
  8. package/agents/build-fixer.md +0 -1
  9. package/agents/code-reviewer-low.md +1 -1
  10. package/agents/code-reviewer.md +1 -1
  11. package/agents/critic.md +1 -1
  12. package/agents/designer-high.md +0 -1
  13. package/agents/designer-low.md +0 -1
  14. package/agents/designer.md +0 -1
  15. package/agents/executor-high.md +0 -1
  16. package/agents/executor-low.md +0 -1
  17. package/agents/executor.md +0 -1
  18. package/agents/explore-high.md +1 -1
  19. package/agents/explore-medium.md +1 -1
  20. package/agents/explore.md +1 -1
  21. package/agents/planner.md +0 -1
  22. package/agents/qa-tester-high.md +0 -1
  23. package/agents/qa-tester.md +0 -1
  24. package/agents/researcher-low.md +1 -1
  25. package/agents/researcher.md +1 -1
  26. package/agents/scientist-high.md +1 -1
  27. package/agents/scientist-low.md +1 -1
  28. package/agents/scientist.md +1 -1
  29. package/agents/security-reviewer-low.md +1 -1
  30. package/agents/security-reviewer.md +1 -1
  31. package/agents/tdd-guide-low.md +0 -1
  32. package/agents/tdd-guide.md +0 -1
  33. package/agents/vision.md +1 -1
  34. package/agents/writer.md +0 -1
  35. package/bridge/mcp-server.cjs +1885 -76
  36. package/commands/omc-setup.md +39 -0
  37. package/dist/mcp/standalone-server.js +13 -5
  38. package/dist/mcp/standalone-server.js.map +1 -1
  39. package/dist/tools/ast-tools.d.ts.map +1 -1
  40. package/dist/tools/ast-tools.js +35 -1
  41. package/dist/tools/ast-tools.js.map +1 -1
  42. package/package.json +1 -1
@@ -407,11 +407,11 @@ var require_codegen = __commonJS({
407
407
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
408
408
  return `${varKind} ${this.name}${rhs};` + _n;
409
409
  }
410
- optimizeNames(names, constants) {
410
+ optimizeNames(names, constants2) {
411
411
  if (!names[this.name.str])
412
412
  return;
413
413
  if (this.rhs)
414
- this.rhs = optimizeExpr(this.rhs, names, constants);
414
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
415
415
  return this;
416
416
  }
417
417
  get names() {
@@ -428,10 +428,10 @@ var require_codegen = __commonJS({
428
428
  render({ _n }) {
429
429
  return `${this.lhs} = ${this.rhs};` + _n;
430
430
  }
431
- optimizeNames(names, constants) {
431
+ optimizeNames(names, constants2) {
432
432
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
433
433
  return;
434
- this.rhs = optimizeExpr(this.rhs, names, constants);
434
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
435
435
  return this;
436
436
  }
437
437
  get names() {
@@ -492,8 +492,8 @@ var require_codegen = __commonJS({
492
492
  optimizeNodes() {
493
493
  return `${this.code}` ? this : void 0;
494
494
  }
495
- optimizeNames(names, constants) {
496
- this.code = optimizeExpr(this.code, names, constants);
495
+ optimizeNames(names, constants2) {
496
+ this.code = optimizeExpr(this.code, names, constants2);
497
497
  return this;
498
498
  }
499
499
  get names() {
@@ -522,12 +522,12 @@ var require_codegen = __commonJS({
522
522
  }
523
523
  return nodes.length > 0 ? this : void 0;
524
524
  }
525
- optimizeNames(names, constants) {
525
+ optimizeNames(names, constants2) {
526
526
  const { nodes } = this;
527
527
  let i = nodes.length;
528
528
  while (i--) {
529
529
  const n = nodes[i];
530
- if (n.optimizeNames(names, constants))
530
+ if (n.optimizeNames(names, constants2))
531
531
  continue;
532
532
  subtractNames(names, n.names);
533
533
  nodes.splice(i, 1);
@@ -580,12 +580,12 @@ var require_codegen = __commonJS({
580
580
  return void 0;
581
581
  return this;
582
582
  }
583
- optimizeNames(names, constants) {
583
+ optimizeNames(names, constants2) {
584
584
  var _a;
585
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants);
586
- if (!(super.optimizeNames(names, constants) || this.else))
585
+ this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants2);
586
+ if (!(super.optimizeNames(names, constants2) || this.else))
587
587
  return;
588
- this.condition = optimizeExpr(this.condition, names, constants);
588
+ this.condition = optimizeExpr(this.condition, names, constants2);
589
589
  return this;
590
590
  }
591
591
  get names() {
@@ -608,10 +608,10 @@ var require_codegen = __commonJS({
608
608
  render(opts) {
609
609
  return `for(${this.iteration})` + super.render(opts);
610
610
  }
611
- optimizeNames(names, constants) {
612
- if (!super.optimizeNames(names, constants))
611
+ optimizeNames(names, constants2) {
612
+ if (!super.optimizeNames(names, constants2))
613
613
  return;
614
- this.iteration = optimizeExpr(this.iteration, names, constants);
614
+ this.iteration = optimizeExpr(this.iteration, names, constants2);
615
615
  return this;
616
616
  }
617
617
  get names() {
@@ -647,10 +647,10 @@ var require_codegen = __commonJS({
647
647
  render(opts) {
648
648
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
649
649
  }
650
- optimizeNames(names, constants) {
651
- if (!super.optimizeNames(names, constants))
650
+ optimizeNames(names, constants2) {
651
+ if (!super.optimizeNames(names, constants2))
652
652
  return;
653
- this.iterable = optimizeExpr(this.iterable, names, constants);
653
+ this.iterable = optimizeExpr(this.iterable, names, constants2);
654
654
  return this;
655
655
  }
656
656
  get names() {
@@ -692,11 +692,11 @@ var require_codegen = __commonJS({
692
692
  (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
693
693
  return this;
694
694
  }
695
- optimizeNames(names, constants) {
695
+ optimizeNames(names, constants2) {
696
696
  var _a, _b;
697
- super.optimizeNames(names, constants);
698
- (_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants);
699
- (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants);
697
+ super.optimizeNames(names, constants2);
698
+ (_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants2);
699
+ (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants2);
700
700
  return this;
701
701
  }
702
702
  get names() {
@@ -997,7 +997,7 @@ var require_codegen = __commonJS({
997
997
  function addExprNames(names, from) {
998
998
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
999
999
  }
1000
- function optimizeExpr(expr, names, constants) {
1000
+ function optimizeExpr(expr, names, constants2) {
1001
1001
  if (expr instanceof code_1.Name)
1002
1002
  return replaceName(expr);
1003
1003
  if (!canOptimize(expr))
@@ -1012,14 +1012,14 @@ var require_codegen = __commonJS({
1012
1012
  return items;
1013
1013
  }, []));
1014
1014
  function replaceName(n) {
1015
- const c = constants[n.str];
1015
+ const c = constants2[n.str];
1016
1016
  if (c === void 0 || names[n.str] !== 1)
1017
1017
  return n;
1018
1018
  delete names[n.str];
1019
1019
  return c;
1020
1020
  }
1021
1021
  function canOptimize(e) {
1022
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== void 0);
1022
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== void 0);
1023
1023
  }
1024
1024
  }
1025
1025
  function subtractNames(names, from) {
@@ -2981,7 +2981,7 @@ var require_compile = __commonJS({
2981
2981
  const schOrFunc = root.refs[ref];
2982
2982
  if (schOrFunc)
2983
2983
  return schOrFunc;
2984
- let _sch = resolve2.call(this, root, ref);
2984
+ let _sch = resolve4.call(this, root, ref);
2985
2985
  if (_sch === void 0) {
2986
2986
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2987
2987
  const { schemaId } = this.opts;
@@ -3008,7 +3008,7 @@ var require_compile = __commonJS({
3008
3008
  function sameSchemaEnv(s1, s2) {
3009
3009
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3010
3010
  }
3011
- function resolve2(root, ref) {
3011
+ function resolve4(root, ref) {
3012
3012
  let sch;
3013
3013
  while (typeof (sch = this.refs[ref]) == "string")
3014
3014
  ref = sch;
@@ -3223,8 +3223,8 @@ var require_utils = __commonJS({
3223
3223
  }
3224
3224
  return ind;
3225
3225
  }
3226
- function removeDotSegments(path) {
3227
- let input = path;
3226
+ function removeDotSegments(path6) {
3227
+ let input = path6;
3228
3228
  const output = [];
3229
3229
  let nextSlash = -1;
3230
3230
  let len = 0;
@@ -3423,8 +3423,8 @@ var require_schemes = __commonJS({
3423
3423
  wsComponent.secure = void 0;
3424
3424
  }
3425
3425
  if (wsComponent.resourceName) {
3426
- const [path, query] = wsComponent.resourceName.split("?");
3427
- wsComponent.path = path && path !== "/" ? path : void 0;
3426
+ const [path6, query] = wsComponent.resourceName.split("?");
3427
+ wsComponent.path = path6 && path6 !== "/" ? path6 : void 0;
3428
3428
  wsComponent.query = query;
3429
3429
  wsComponent.resourceName = void 0;
3430
3430
  }
@@ -3576,24 +3576,24 @@ var require_fast_uri = __commonJS({
3576
3576
  function normalize(uri, options) {
3577
3577
  if (typeof uri === "string") {
3578
3578
  uri = /** @type {T} */
3579
- serialize(parse4(uri, options), options);
3579
+ serialize(parse5(uri, options), options);
3580
3580
  } else if (typeof uri === "object") {
3581
3581
  uri = /** @type {T} */
3582
- parse4(serialize(uri, options), options);
3582
+ parse5(serialize(uri, options), options);
3583
3583
  }
3584
3584
  return uri;
3585
3585
  }
3586
- function resolve2(baseURI, relativeURI, options) {
3586
+ function resolve4(baseURI, relativeURI, options) {
3587
3587
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3588
- const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
3588
+ const resolved = resolveComponent(parse5(baseURI, schemelessOptions), parse5(relativeURI, schemelessOptions), schemelessOptions, true);
3589
3589
  schemelessOptions.skipEscape = true;
3590
3590
  return serialize(resolved, schemelessOptions);
3591
3591
  }
3592
3592
  function resolveComponent(base, relative, options, skipNormalization) {
3593
3593
  const target = {};
3594
3594
  if (!skipNormalization) {
3595
- base = parse4(serialize(base, options), options);
3596
- relative = parse4(serialize(relative, options), options);
3595
+ base = parse5(serialize(base, options), options);
3596
+ relative = parse5(serialize(relative, options), options);
3597
3597
  }
3598
3598
  options = options || {};
3599
3599
  if (!options.tolerant && relative.scheme) {
@@ -3645,13 +3645,13 @@ var require_fast_uri = __commonJS({
3645
3645
  function equal(uriA, uriB, options) {
3646
3646
  if (typeof uriA === "string") {
3647
3647
  uriA = unescape(uriA);
3648
- uriA = serialize(normalizeComponentEncoding(parse4(uriA, options), true), { ...options, skipEscape: true });
3648
+ uriA = serialize(normalizeComponentEncoding(parse5(uriA, options), true), { ...options, skipEscape: true });
3649
3649
  } else if (typeof uriA === "object") {
3650
3650
  uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
3651
3651
  }
3652
3652
  if (typeof uriB === "string") {
3653
3653
  uriB = unescape(uriB);
3654
- uriB = serialize(normalizeComponentEncoding(parse4(uriB, options), true), { ...options, skipEscape: true });
3654
+ uriB = serialize(normalizeComponentEncoding(parse5(uriB, options), true), { ...options, skipEscape: true });
3655
3655
  } else if (typeof uriB === "object") {
3656
3656
  uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
3657
3657
  }
@@ -3720,7 +3720,7 @@ var require_fast_uri = __commonJS({
3720
3720
  return uriTokens.join("");
3721
3721
  }
3722
3722
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
3723
- function parse4(uri, opts) {
3723
+ function parse5(uri, opts) {
3724
3724
  const options = Object.assign({}, opts);
3725
3725
  const parsed = {
3726
3726
  scheme: void 0,
@@ -3810,11 +3810,11 @@ var require_fast_uri = __commonJS({
3810
3810
  var fastUri = {
3811
3811
  SCHEMES,
3812
3812
  normalize,
3813
- resolve: resolve2,
3813
+ resolve: resolve4,
3814
3814
  resolveComponent,
3815
3815
  equal,
3816
3816
  serialize,
3817
- parse: parse4
3817
+ parse: parse5
3818
3818
  };
3819
3819
  module2.exports = fastUri;
3820
3820
  module2.exports.default = fastUri;
@@ -6777,12 +6777,12 @@ var require_dist = __commonJS({
6777
6777
  throw new Error(`Unknown format "${name}"`);
6778
6778
  return f;
6779
6779
  };
6780
- function addFormats(ajv, list, fs, exportName) {
6780
+ function addFormats(ajv, list, fs5, exportName) {
6781
6781
  var _a;
6782
6782
  var _b;
6783
6783
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
6784
6784
  for (const f of list)
6785
- ajv.addFormat(f, fs[f]);
6785
+ ajv.addFormat(f, fs5[f]);
6786
6786
  }
6787
6787
  module2.exports = exports2 = formatsPlugin;
6788
6788
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -7268,8 +7268,8 @@ function getErrorMap() {
7268
7268
 
7269
7269
  // node_modules/zod/v3/helpers/parseUtil.js
7270
7270
  var makeIssue = (params) => {
7271
- const { data, path, errorMaps, issueData } = params;
7272
- const fullPath = [...path, ...issueData.path || []];
7271
+ const { data, path: path6, errorMaps, issueData } = params;
7272
+ const fullPath = [...path6, ...issueData.path || []];
7273
7273
  const fullIssue = {
7274
7274
  ...issueData,
7275
7275
  path: fullPath
@@ -7385,11 +7385,11 @@ var errorUtil;
7385
7385
 
7386
7386
  // node_modules/zod/v3/types.js
7387
7387
  var ParseInputLazyPath = class {
7388
- constructor(parent, value, path, key) {
7388
+ constructor(parent, value, path6, key) {
7389
7389
  this._cachedPath = [];
7390
7390
  this.parent = parent;
7391
7391
  this.data = value;
7392
- this._path = path;
7392
+ this._path = path6;
7393
7393
  this._key = key;
7394
7394
  }
7395
7395
  get path() {
@@ -11026,10 +11026,10 @@ function assignProp(target, prop, value) {
11026
11026
  configurable: true
11027
11027
  });
11028
11028
  }
11029
- function getElementAtPath(obj, path) {
11030
- if (!path)
11029
+ function getElementAtPath(obj, path6) {
11030
+ if (!path6)
11031
11031
  return obj;
11032
- return path.reduce((acc, key) => acc?.[key], obj);
11032
+ return path6.reduce((acc, key) => acc?.[key], obj);
11033
11033
  }
11034
11034
  function promiseAllObject(promisesObj) {
11035
11035
  const keys = Object.keys(promisesObj);
@@ -11349,11 +11349,11 @@ function aborted(x, startIndex = 0) {
11349
11349
  }
11350
11350
  return false;
11351
11351
  }
11352
- function prefixIssues(path, issues) {
11352
+ function prefixIssues(path6, issues) {
11353
11353
  return issues.map((iss) => {
11354
11354
  var _a;
11355
11355
  (_a = iss).path ?? (_a.path = []);
11356
- iss.path.unshift(path);
11356
+ iss.path.unshift(path6);
11357
11357
  return iss;
11358
11358
  });
11359
11359
  }
@@ -16640,7 +16640,7 @@ var Protocol = class {
16640
16640
  return;
16641
16641
  }
16642
16642
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
16643
- await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
16643
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
16644
16644
  options?.signal?.throwIfAborted();
16645
16645
  }
16646
16646
  } catch (error2) {
@@ -16657,7 +16657,7 @@ var Protocol = class {
16657
16657
  */
16658
16658
  request(request, resultSchema, options) {
16659
16659
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
16660
- return new Promise((resolve2, reject) => {
16660
+ return new Promise((resolve4, reject) => {
16661
16661
  const earlyReject = (error2) => {
16662
16662
  reject(error2);
16663
16663
  };
@@ -16735,7 +16735,7 @@ var Protocol = class {
16735
16735
  if (!parseResult.success) {
16736
16736
  reject(parseResult.error);
16737
16737
  } else {
16738
- resolve2(parseResult.data);
16738
+ resolve4(parseResult.data);
16739
16739
  }
16740
16740
  } catch (error2) {
16741
16741
  reject(error2);
@@ -16996,12 +16996,12 @@ var Protocol = class {
16996
16996
  }
16997
16997
  } catch {
16998
16998
  }
16999
- return new Promise((resolve2, reject) => {
16999
+ return new Promise((resolve4, reject) => {
17000
17000
  if (signal.aborted) {
17001
17001
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
17002
17002
  return;
17003
17003
  }
17004
- const timeoutId = setTimeout(resolve2, interval);
17004
+ const timeoutId = setTimeout(resolve4, interval);
17005
17005
  signal.addEventListener("abort", () => {
17006
17006
  clearTimeout(timeoutId);
17007
17007
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -17730,12 +17730,12 @@ var StdioServerTransport = class {
17730
17730
  this.onclose?.();
17731
17731
  }
17732
17732
  send(message) {
17733
- return new Promise((resolve2) => {
17733
+ return new Promise((resolve4) => {
17734
17734
  const json = serializeMessage(message);
17735
17735
  if (this._stdout.write(json)) {
17736
- resolve2();
17736
+ resolve4();
17737
17737
  } else {
17738
- this._stdout.once("drain", resolve2);
17738
+ this._stdout.once("drain", resolve4);
17739
17739
  }
17740
17740
  });
17741
17741
  }
@@ -17874,7 +17874,7 @@ var LspClient = class {
17874
17874
  Install with: ${this.serverConfig.installHint}`
17875
17875
  );
17876
17876
  }
17877
- return new Promise((resolve2, reject) => {
17877
+ return new Promise((resolve4, reject) => {
17878
17878
  this.process = (0, import_child_process2.spawn)(this.serverConfig.command, this.serverConfig.args, {
17879
17879
  cwd: this.workspaceRoot,
17880
17880
  stdio: ["pipe", "pipe", "pipe"]
@@ -17897,7 +17897,7 @@ Install with: ${this.serverConfig.installHint}`
17897
17897
  });
17898
17898
  this.initialize().then(() => {
17899
17899
  this.initialized = true;
17900
- resolve2();
17900
+ resolve4();
17901
17901
  }).catch(reject);
17902
17902
  });
17903
17903
  }
@@ -17993,13 +17993,13 @@ Install with: ${this.serverConfig.installHint}`
17993
17993
  const message = `Content-Length: ${Buffer.byteLength(content)}\r
17994
17994
  \r
17995
17995
  ${content}`;
17996
- return new Promise((resolve2, reject) => {
17996
+ return new Promise((resolve4, reject) => {
17997
17997
  const timeoutHandle = setTimeout(() => {
17998
17998
  this.pendingRequests.delete(id);
17999
17999
  reject(new Error(`LSP request '${method}' timed out after ${timeout}ms`));
18000
18000
  }, timeout);
18001
18001
  this.pendingRequests.set(id, {
18002
- resolve: resolve2,
18002
+ resolve: resolve4,
18003
18003
  reject,
18004
18004
  timeout: timeoutHandle
18005
18005
  });
@@ -18068,7 +18068,7 @@ ${content}`;
18068
18068
  }
18069
18069
  });
18070
18070
  this.openDocuments.add(uri);
18071
- await new Promise((resolve2) => setTimeout(resolve2, 100));
18071
+ await new Promise((resolve4) => setTimeout(resolve4, 100));
18072
18072
  }
18073
18073
  /**
18074
18074
  * Close a document
@@ -18320,9 +18320,9 @@ function formatRange(range) {
18320
18320
  return start === end ? start : `${start}-${end}`;
18321
18321
  }
18322
18322
  function formatLocation(location) {
18323
- const path = uriToPath(location.uri);
18323
+ const path6 = uriToPath(location.uri);
18324
18324
  const range = formatRange(location.range);
18325
- return `${path}:${range}`;
18325
+ return `${path6}:${range}`;
18326
18326
  }
18327
18327
  function formatHover(hover) {
18328
18328
  if (!hover) return "No hover information available";
@@ -18408,8 +18408,8 @@ function formatWorkspaceEdit(edit) {
18408
18408
  const lines = [];
18409
18409
  if (edit.changes) {
18410
18410
  for (const [uri, changes] of Object.entries(edit.changes)) {
18411
- const path = uriToPath(uri);
18412
- lines.push(`File: ${path}`);
18411
+ const path6 = uriToPath(uri);
18412
+ lines.push(`File: ${path6}`);
18413
18413
  for (const change of changes) {
18414
18414
  const range = formatRange(change.range);
18415
18415
  const preview = change.newText.length > 50 ? change.newText.slice(0, 50) + "..." : change.newText;
@@ -18419,8 +18419,8 @@ function formatWorkspaceEdit(edit) {
18419
18419
  }
18420
18420
  if (edit.documentChanges) {
18421
18421
  for (const docChange of edit.documentChanges) {
18422
- const path = uriToPath(docChange.textDocument.uri);
18423
- lines.push(`File: ${path}`);
18422
+ const path6 = uriToPath(docChange.textDocument.uri);
18423
+ lines.push(`File: ${path6}`);
18424
18424
  for (const change of docChange.edits) {
18425
18425
  const range = formatRange(change.range);
18426
18426
  const preview = change.newText.length > 50 ? change.newText.slice(0, 50) + "..." : change.newText;
@@ -18549,7 +18549,7 @@ async function runLspAggregatedDiagnostics(directory, extensions = [".ts", ".tsx
18549
18549
  continue;
18550
18550
  }
18551
18551
  await client.openDocument(file);
18552
- await new Promise((resolve2) => setTimeout(resolve2, LSP_DIAGNOSTICS_WAIT_MS));
18552
+ await new Promise((resolve4) => setTimeout(resolve4, LSP_DIAGNOSTICS_WAIT_MS));
18553
18553
  const diagnostics = client.getDiagnostics(file);
18554
18554
  for (const diagnostic of diagnostics) {
18555
18555
  allDiagnostics.push({
@@ -18799,7 +18799,7 @@ var lspDiagnosticsTool = {
18799
18799
  const { file, severity } = args;
18800
18800
  return withLspClient(file, "diagnostics", async (client) => {
18801
18801
  await client.openDocument(file);
18802
- await new Promise((resolve2) => setTimeout(resolve2, LSP_DIAGNOSTICS_WAIT_MS));
18802
+ await new Promise((resolve4) => setTimeout(resolve4, LSP_DIAGNOSTICS_WAIT_MS));
18803
18803
  let diagnostics = client.getDiagnostics(file);
18804
18804
  if (severity) {
18805
18805
  const severityMap = {
@@ -19026,7 +19026,1816 @@ var lspTools = [
19026
19026
  lspCodeActionResolveTool
19027
19027
  ];
19028
19028
 
19029
+ // src/tools/ast-tools.ts
19030
+ var import_fs5 = require("fs");
19031
+ var import_path6 = require("path");
19032
+ var sgModule = null;
19033
+ var sgLoadFailed = false;
19034
+ var sgLoadError = "";
19035
+ async function getSgModule() {
19036
+ if (sgLoadFailed) {
19037
+ return null;
19038
+ }
19039
+ if (!sgModule) {
19040
+ try {
19041
+ sgModule = await import("@ast-grep/napi");
19042
+ } catch (error2) {
19043
+ sgLoadFailed = true;
19044
+ sgLoadError = error2 instanceof Error ? error2.message : String(error2);
19045
+ return null;
19046
+ }
19047
+ }
19048
+ return sgModule;
19049
+ }
19050
+ function toLangEnum(sg, language) {
19051
+ const langMap = {
19052
+ javascript: sg.Lang.JavaScript,
19053
+ typescript: sg.Lang.TypeScript,
19054
+ tsx: sg.Lang.Tsx,
19055
+ python: sg.Lang.Python,
19056
+ ruby: sg.Lang.Ruby,
19057
+ go: sg.Lang.Go,
19058
+ rust: sg.Lang.Rust,
19059
+ java: sg.Lang.Java,
19060
+ kotlin: sg.Lang.Kotlin,
19061
+ swift: sg.Lang.Swift,
19062
+ c: sg.Lang.C,
19063
+ cpp: sg.Lang.Cpp,
19064
+ csharp: sg.Lang.CSharp,
19065
+ html: sg.Lang.Html,
19066
+ css: sg.Lang.Css,
19067
+ json: sg.Lang.Json,
19068
+ yaml: sg.Lang.Yaml
19069
+ };
19070
+ const lang = langMap[language];
19071
+ if (!lang) {
19072
+ throw new Error(`Unsupported language: ${language}`);
19073
+ }
19074
+ return lang;
19075
+ }
19076
+ var SUPPORTED_LANGUAGES = [
19077
+ "javascript",
19078
+ "typescript",
19079
+ "tsx",
19080
+ "python",
19081
+ "ruby",
19082
+ "go",
19083
+ "rust",
19084
+ "java",
19085
+ "kotlin",
19086
+ "swift",
19087
+ "c",
19088
+ "cpp",
19089
+ "csharp",
19090
+ "html",
19091
+ "css",
19092
+ "json",
19093
+ "yaml"
19094
+ ];
19095
+ var EXT_TO_LANG = {
19096
+ ".js": "javascript",
19097
+ ".mjs": "javascript",
19098
+ ".cjs": "javascript",
19099
+ ".jsx": "javascript",
19100
+ ".ts": "typescript",
19101
+ ".mts": "typescript",
19102
+ ".cts": "typescript",
19103
+ ".tsx": "tsx",
19104
+ ".py": "python",
19105
+ ".rb": "ruby",
19106
+ ".go": "go",
19107
+ ".rs": "rust",
19108
+ ".java": "java",
19109
+ ".kt": "kotlin",
19110
+ ".kts": "kotlin",
19111
+ ".swift": "swift",
19112
+ ".c": "c",
19113
+ ".h": "c",
19114
+ ".cpp": "cpp",
19115
+ ".cc": "cpp",
19116
+ ".cxx": "cpp",
19117
+ ".hpp": "cpp",
19118
+ ".cs": "csharp",
19119
+ ".html": "html",
19120
+ ".htm": "html",
19121
+ ".css": "css",
19122
+ ".json": "json",
19123
+ ".yaml": "yaml",
19124
+ ".yml": "yaml"
19125
+ };
19126
+ function getFilesForLanguage(dirPath, language, maxFiles = 1e3) {
19127
+ const files = [];
19128
+ const extensions = Object.entries(EXT_TO_LANG).filter(([_, lang]) => lang === language).map(([ext]) => ext);
19129
+ function walk(dir) {
19130
+ if (files.length >= maxFiles) return;
19131
+ try {
19132
+ const entries = (0, import_fs5.readdirSync)(dir, { withFileTypes: true });
19133
+ for (const entry of entries) {
19134
+ if (files.length >= maxFiles) return;
19135
+ const fullPath = (0, import_path6.join)(dir, entry.name);
19136
+ if (entry.isDirectory()) {
19137
+ if (![
19138
+ "node_modules",
19139
+ ".git",
19140
+ "dist",
19141
+ "build",
19142
+ "__pycache__",
19143
+ ".venv",
19144
+ "venv"
19145
+ ].includes(entry.name)) {
19146
+ walk(fullPath);
19147
+ }
19148
+ } else if (entry.isFile()) {
19149
+ const ext = (0, import_path6.extname)(entry.name).toLowerCase();
19150
+ if (extensions.includes(ext)) {
19151
+ files.push(fullPath);
19152
+ }
19153
+ }
19154
+ }
19155
+ } catch {
19156
+ }
19157
+ }
19158
+ const resolvedPath = (0, import_path6.resolve)(dirPath);
19159
+ const stat = (0, import_fs5.statSync)(resolvedPath);
19160
+ if (stat.isFile()) {
19161
+ return [resolvedPath];
19162
+ }
19163
+ walk(resolvedPath);
19164
+ return files;
19165
+ }
19166
+ function formatMatch(filePath, matchText, startLine, endLine, context, fileContent) {
19167
+ const lines = fileContent.split("\n");
19168
+ const contextStart = Math.max(0, startLine - context - 1);
19169
+ const contextEnd = Math.min(lines.length, endLine + context);
19170
+ const contextLines = lines.slice(contextStart, contextEnd);
19171
+ const numberedLines = contextLines.map((line, i) => {
19172
+ const lineNum = contextStart + i + 1;
19173
+ const isMatch = lineNum >= startLine && lineNum <= endLine;
19174
+ const prefix = isMatch ? ">" : " ";
19175
+ return `${prefix} ${lineNum.toString().padStart(4)}: ${line}`;
19176
+ });
19177
+ return `${filePath}:${startLine}
19178
+ ${numberedLines.join("\n")}`;
19179
+ }
19180
+ var astGrepSearchTool = {
19181
+ name: "ast_grep_search",
19182
+ description: `Search for code patterns using AST matching. More precise than text search.
19183
+
19184
+ Use meta-variables in patterns:
19185
+ - $NAME - matches any single AST node (identifier, expression, etc.)
19186
+ - $$$ARGS - matches multiple nodes (for function arguments, list items, etc.)
19187
+
19188
+ Examples:
19189
+ - "function $NAME($$$ARGS)" - find all function declarations
19190
+ - "console.log($MSG)" - find all console.log calls
19191
+ - "if ($COND) { $$$BODY }" - find all if statements
19192
+ - "$X === null" - find null equality checks
19193
+ - "import $$$IMPORTS from '$MODULE'" - find imports
19194
+
19195
+ Note: Patterns must be valid AST nodes for the language.`,
19196
+ schema: {
19197
+ pattern: external_exports.string().describe("AST pattern with meta-variables ($VAR, $$$VARS)"),
19198
+ language: external_exports.enum(SUPPORTED_LANGUAGES).describe("Programming language"),
19199
+ path: external_exports.string().optional().describe("Directory or file to search (default: current directory)"),
19200
+ context: external_exports.number().int().min(0).max(10).optional().describe("Lines of context around matches (default: 2)"),
19201
+ maxResults: external_exports.number().int().min(1).max(100).optional().describe("Maximum results to return (default: 20)")
19202
+ },
19203
+ handler: async (args) => {
19204
+ const {
19205
+ pattern,
19206
+ language,
19207
+ path: path6 = ".",
19208
+ context = 2,
19209
+ maxResults = 20
19210
+ } = args;
19211
+ try {
19212
+ const sg = await getSgModule();
19213
+ if (!sg) {
19214
+ return {
19215
+ content: [
19216
+ {
19217
+ type: "text",
19218
+ text: `@ast-grep/napi is not available. Install it with: npm install -g @ast-grep/napi
19219
+ Error: ${sgLoadError}`
19220
+ }
19221
+ ]
19222
+ };
19223
+ }
19224
+ const files = getFilesForLanguage(path6, language);
19225
+ if (files.length === 0) {
19226
+ return {
19227
+ content: [
19228
+ {
19229
+ type: "text",
19230
+ text: `No ${language} files found in ${path6}`
19231
+ }
19232
+ ]
19233
+ };
19234
+ }
19235
+ const results = [];
19236
+ let totalMatches = 0;
19237
+ for (const filePath of files) {
19238
+ if (totalMatches >= maxResults) break;
19239
+ try {
19240
+ const content = (0, import_fs5.readFileSync)(filePath, "utf-8");
19241
+ const root = sg.parse(toLangEnum(sg, language), content).root();
19242
+ const matches = root.findAll(pattern);
19243
+ for (const match of matches) {
19244
+ if (totalMatches >= maxResults) break;
19245
+ const range = match.range();
19246
+ const startLine = range.start.line + 1;
19247
+ const endLine = range.end.line + 1;
19248
+ results.push(
19249
+ formatMatch(
19250
+ filePath,
19251
+ match.text(),
19252
+ startLine,
19253
+ endLine,
19254
+ context,
19255
+ content
19256
+ )
19257
+ );
19258
+ totalMatches++;
19259
+ }
19260
+ } catch {
19261
+ }
19262
+ }
19263
+ if (results.length === 0) {
19264
+ return {
19265
+ content: [
19266
+ {
19267
+ type: "text",
19268
+ text: `No matches found for pattern: ${pattern}
19269
+
19270
+ Searched ${files.length} ${language} file(s) in ${path6}
19271
+
19272
+ Tip: Ensure the pattern is a valid AST node. For example:
19273
+ - Use "function $NAME" not just "$NAME"
19274
+ - Use "console.log($X)" not "console.log"`
19275
+ }
19276
+ ]
19277
+ };
19278
+ }
19279
+ const header = `Found ${totalMatches} match(es) in ${files.length} file(s)
19280
+ Pattern: ${pattern}
19281
+
19282
+ `;
19283
+ return {
19284
+ content: [
19285
+ {
19286
+ type: "text",
19287
+ text: header + results.join("\n\n---\n\n")
19288
+ }
19289
+ ]
19290
+ };
19291
+ } catch (error2) {
19292
+ return {
19293
+ content: [
19294
+ {
19295
+ type: "text",
19296
+ text: `Error in AST search: ${error2 instanceof Error ? error2.message : String(error2)}
19297
+
19298
+ Common issues:
19299
+ - Pattern must be a complete AST node
19300
+ - Language must match file type
19301
+ - Check that @ast-grep/napi is installed`
19302
+ }
19303
+ ]
19304
+ };
19305
+ }
19306
+ }
19307
+ };
19308
+ var astGrepReplaceTool = {
19309
+ name: "ast_grep_replace",
19310
+ description: `Replace code patterns using AST matching. Preserves matched content via meta-variables.
19311
+
19312
+ Use meta-variables in both pattern and replacement:
19313
+ - $NAME in pattern captures a node, use $NAME in replacement to insert it
19314
+ - $$$ARGS captures multiple nodes
19315
+
19316
+ Examples:
19317
+ - Pattern: "console.log($MSG)" \u2192 Replacement: "logger.info($MSG)"
19318
+ - Pattern: "var $NAME = $VALUE" \u2192 Replacement: "const $NAME = $VALUE"
19319
+ - Pattern: "$OBJ.forEach(($ITEM) => { $$$BODY })" \u2192 Replacement: "for (const $ITEM of $OBJ) { $$$BODY }"
19320
+
19321
+ IMPORTANT: dryRun=true (default) only previews changes. Set dryRun=false to apply.`,
19322
+ schema: {
19323
+ pattern: external_exports.string().describe("Pattern to match"),
19324
+ replacement: external_exports.string().describe("Replacement pattern (use same meta-variables)"),
19325
+ language: external_exports.enum(SUPPORTED_LANGUAGES).describe("Programming language"),
19326
+ path: external_exports.string().optional().describe("Directory or file to search (default: current directory)"),
19327
+ dryRun: external_exports.boolean().optional().describe("Preview only, don't apply changes (default: true)")
19328
+ },
19329
+ handler: async (args) => {
19330
+ const { pattern, replacement, language, path: path6 = ".", dryRun = true } = args;
19331
+ try {
19332
+ const sg = await getSgModule();
19333
+ if (!sg) {
19334
+ return {
19335
+ content: [
19336
+ {
19337
+ type: "text",
19338
+ text: `@ast-grep/napi is not available. Install it with: npm install -g @ast-grep/napi
19339
+ Error: ${sgLoadError}`
19340
+ }
19341
+ ]
19342
+ };
19343
+ }
19344
+ const files = getFilesForLanguage(path6, language);
19345
+ if (files.length === 0) {
19346
+ return {
19347
+ content: [
19348
+ {
19349
+ type: "text",
19350
+ text: `No ${language} files found in ${path6}`
19351
+ }
19352
+ ]
19353
+ };
19354
+ }
19355
+ const changes = [];
19356
+ let totalReplacements = 0;
19357
+ for (const filePath of files) {
19358
+ try {
19359
+ const content = (0, import_fs5.readFileSync)(filePath, "utf-8");
19360
+ const root = sg.parse(toLangEnum(sg, language), content).root();
19361
+ const matches = root.findAll(pattern);
19362
+ if (matches.length === 0) continue;
19363
+ const edits = [];
19364
+ for (const match of matches) {
19365
+ const range = match.range();
19366
+ const startOffset = range.start.index;
19367
+ const endOffset = range.end.index;
19368
+ let finalReplacement = replacement;
19369
+ const matchedText = match.text();
19370
+ try {
19371
+ const metaVars = replacement.match(/\$\$?\$?[A-Z_][A-Z0-9_]*/g) || [];
19372
+ for (const metaVar of metaVars) {
19373
+ const varName = metaVar.replace(/^\$+/, "");
19374
+ const captured = match.getMatch(varName);
19375
+ if (captured) {
19376
+ finalReplacement = finalReplacement.replace(
19377
+ metaVar,
19378
+ captured.text()
19379
+ );
19380
+ }
19381
+ }
19382
+ } catch {
19383
+ }
19384
+ edits.push({
19385
+ start: startOffset,
19386
+ end: endOffset,
19387
+ replacement: finalReplacement,
19388
+ line: range.start.line + 1,
19389
+ before: matchedText
19390
+ });
19391
+ }
19392
+ edits.sort((a, b) => b.start - a.start);
19393
+ let newContent = content;
19394
+ for (const edit of edits) {
19395
+ const before = newContent.slice(edit.start, edit.end);
19396
+ newContent = newContent.slice(0, edit.start) + edit.replacement + newContent.slice(edit.end);
19397
+ changes.push({
19398
+ file: filePath,
19399
+ before,
19400
+ after: edit.replacement,
19401
+ line: edit.line
19402
+ });
19403
+ totalReplacements++;
19404
+ }
19405
+ if (!dryRun && edits.length > 0) {
19406
+ (0, import_fs5.writeFileSync)(filePath, newContent, "utf-8");
19407
+ }
19408
+ } catch {
19409
+ }
19410
+ }
19411
+ if (changes.length === 0) {
19412
+ return {
19413
+ content: [
19414
+ {
19415
+ type: "text",
19416
+ text: `No matches found for pattern: ${pattern}
19417
+
19418
+ Searched ${files.length} ${language} file(s) in ${path6}`
19419
+ }
19420
+ ]
19421
+ };
19422
+ }
19423
+ const mode = dryRun ? "DRY RUN (no changes applied)" : "CHANGES APPLIED";
19424
+ const header = `${mode}
19425
+
19426
+ Found ${totalReplacements} replacement(s) in ${files.length} file(s)
19427
+ Pattern: ${pattern}
19428
+ Replacement: ${replacement}
19429
+
19430
+ `;
19431
+ const changeList = changes.slice(0, 50).map((c) => `${c.file}:${c.line}
19432
+ - ${c.before}
19433
+ + ${c.after}`).join("\n\n");
19434
+ const footer = changes.length > 50 ? `
19435
+
19436
+ ... and ${changes.length - 50} more changes` : "";
19437
+ return {
19438
+ content: [
19439
+ {
19440
+ type: "text",
19441
+ text: header + changeList + footer + (dryRun ? "\n\nTo apply changes, run with dryRun: false" : "")
19442
+ }
19443
+ ]
19444
+ };
19445
+ } catch (error2) {
19446
+ return {
19447
+ content: [
19448
+ {
19449
+ type: "text",
19450
+ text: `Error in AST replace: ${error2 instanceof Error ? error2.message : String(error2)}`
19451
+ }
19452
+ ]
19453
+ };
19454
+ }
19455
+ }
19456
+ };
19457
+ var astTools = [astGrepSearchTool, astGrepReplaceTool];
19458
+
19459
+ // src/tools/python-repl/paths.ts
19460
+ var fs = __toESM(require("fs"), 1);
19461
+ var path = __toESM(require("path"), 1);
19462
+ var os = __toESM(require("os"), 1);
19463
+ var crypto = __toESM(require("crypto"), 1);
19464
+ var SHORT_SESSION_ID_LENGTH = 12;
19465
+ var WINDOWS_RESERVED_NAMES = /* @__PURE__ */ new Set([
19466
+ // Standard reserved device names
19467
+ "CON",
19468
+ "PRN",
19469
+ "AUX",
19470
+ "NUL",
19471
+ "COM1",
19472
+ "COM2",
19473
+ "COM3",
19474
+ "COM4",
19475
+ "COM5",
19476
+ "COM6",
19477
+ "COM7",
19478
+ "COM8",
19479
+ "COM9",
19480
+ "LPT1",
19481
+ "LPT2",
19482
+ "LPT3",
19483
+ "LPT4",
19484
+ "LPT5",
19485
+ "LPT6",
19486
+ "LPT7",
19487
+ "LPT8",
19488
+ "LPT9"
19489
+ ]);
19490
+ function isSecureRuntimeDir(dir) {
19491
+ if (!path.isAbsolute(dir)) return false;
19492
+ try {
19493
+ const stat = fs.lstatSync(dir);
19494
+ if (!stat.isDirectory() || stat.isSymbolicLink()) return false;
19495
+ if (stat.uid !== process.getuid?.()) return false;
19496
+ if ((stat.mode & 511) !== 448) return false;
19497
+ return true;
19498
+ } catch {
19499
+ return false;
19500
+ }
19501
+ }
19502
+ function getRuntimeDir() {
19503
+ const xdgRuntime = process.env.XDG_RUNTIME_DIR;
19504
+ if (xdgRuntime && isSecureRuntimeDir(xdgRuntime)) {
19505
+ return path.join(xdgRuntime, "omc");
19506
+ }
19507
+ const platform = process.platform;
19508
+ if (platform === "darwin") {
19509
+ return path.join(os.homedir(), "Library", "Caches", "omc", "runtime");
19510
+ } else if (platform === "linux") {
19511
+ return path.join("/tmp", "omc", "runtime");
19512
+ } else if (platform === "win32") {
19513
+ const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
19514
+ return path.join(localAppData, "omc", "runtime");
19515
+ }
19516
+ return path.join(os.tmpdir(), "omc", "runtime");
19517
+ }
19518
+ function shortenSessionId(sessionId) {
19519
+ return crypto.createHash("sha256").update(sessionId).digest("hex").slice(0, SHORT_SESSION_ID_LENGTH);
19520
+ }
19521
+ function getSessionDir(sessionId) {
19522
+ const shortId = shortenSessionId(sessionId);
19523
+ return path.join(getRuntimeDir(), shortId);
19524
+ }
19525
+ function getBridgeSocketPath(sessionId) {
19526
+ return path.join(getSessionDir(sessionId), "bridge.sock");
19527
+ }
19528
+ function getBridgeMetaPath(sessionId) {
19529
+ return path.join(getSessionDir(sessionId), "bridge_meta.json");
19530
+ }
19531
+ function getSessionLockPath(sessionId) {
19532
+ return path.join(getSessionDir(sessionId), "session.lock");
19533
+ }
19534
+ function validatePathSegment(segment, name) {
19535
+ if (!segment || typeof segment !== "string") {
19536
+ throw new Error(`${name} is required and must be a string`);
19537
+ }
19538
+ if (segment.trim().length === 0) {
19539
+ throw new Error(`Invalid ${name}: cannot be empty or whitespace`);
19540
+ }
19541
+ const normalized = segment.normalize("NFC");
19542
+ if (normalized.includes("..") || normalized.includes("/") || normalized.includes("\\")) {
19543
+ throw new Error(`Invalid ${name}: contains path traversal characters`);
19544
+ }
19545
+ if (normalized.includes("\0")) {
19546
+ throw new Error(`Invalid ${name}: contains null byte`);
19547
+ }
19548
+ if (Buffer.byteLength(normalized, "utf8") > 255) {
19549
+ throw new Error(`Invalid ${name}: exceeds maximum length of 255 bytes`);
19550
+ }
19551
+ const upperSegment = normalized.toUpperCase();
19552
+ const baseName = upperSegment.split(".")[0].replace(/[ .]+$/, "");
19553
+ if (WINDOWS_RESERVED_NAMES.has(baseName)) {
19554
+ throw new Error(`${name} contains Windows reserved name: ${segment}`);
19555
+ }
19556
+ if (normalized.endsWith(".") || normalized.endsWith(" ")) {
19557
+ throw new Error(`${name} has trailing dot or space: ${segment}`);
19558
+ }
19559
+ }
19560
+
19561
+ // src/tools/python-repl/session-lock.ts
19562
+ var fs3 = __toESM(require("fs/promises"), 1);
19563
+ var fsSync2 = __toESM(require("fs"), 1);
19564
+ var path4 = __toESM(require("path"), 1);
19565
+ var os2 = __toESM(require("os"), 1);
19566
+ var crypto3 = __toESM(require("crypto"), 1);
19567
+ var import_child_process5 = require("child_process");
19568
+ var import_util6 = require("util");
19569
+
19570
+ // src/lib/atomic-write.ts
19571
+ var fs2 = __toESM(require("fs/promises"), 1);
19572
+ var fsSync = __toESM(require("fs"), 1);
19573
+ var path2 = __toESM(require("path"), 1);
19574
+ var crypto2 = __toESM(require("crypto"), 1);
19575
+ function ensureDirSync(dir) {
19576
+ if (fsSync.existsSync(dir)) {
19577
+ return;
19578
+ }
19579
+ try {
19580
+ fsSync.mkdirSync(dir, { recursive: true });
19581
+ } catch (err) {
19582
+ if (err.code === "EEXIST") {
19583
+ return;
19584
+ }
19585
+ throw err;
19586
+ }
19587
+ }
19588
+ async function atomicWriteJson(filePath, data) {
19589
+ const dir = path2.dirname(filePath);
19590
+ const base = path2.basename(filePath);
19591
+ const tempPath = path2.join(dir, `.${base}.tmp.${crypto2.randomUUID()}`);
19592
+ let success = false;
19593
+ try {
19594
+ ensureDirSync(dir);
19595
+ const jsonContent = JSON.stringify(data, null, 2);
19596
+ const fd = await fs2.open(tempPath, "wx", 384);
19597
+ try {
19598
+ await fd.write(jsonContent, 0, "utf-8");
19599
+ await fd.sync();
19600
+ } finally {
19601
+ await fd.close();
19602
+ }
19603
+ await fs2.rename(tempPath, filePath);
19604
+ success = true;
19605
+ try {
19606
+ const dirFd = await fs2.open(dir, "r");
19607
+ try {
19608
+ await dirFd.sync();
19609
+ } finally {
19610
+ await dirFd.close();
19611
+ }
19612
+ } catch {
19613
+ }
19614
+ } finally {
19615
+ if (!success) {
19616
+ await fs2.unlink(tempPath).catch(() => {
19617
+ });
19618
+ }
19619
+ }
19620
+ }
19621
+ async function safeReadJson(filePath) {
19622
+ try {
19623
+ await fs2.access(filePath);
19624
+ const content = await fs2.readFile(filePath, "utf-8");
19625
+ return JSON.parse(content);
19626
+ } catch (err) {
19627
+ const error2 = err;
19628
+ if (error2.code === "ENOENT") {
19629
+ return null;
19630
+ }
19631
+ return null;
19632
+ }
19633
+ }
19634
+
19635
+ // src/platform/index.ts
19636
+ var path3 = __toESM(require("path"), 1);
19637
+
19638
+ // src/platform/process-utils.ts
19639
+ var import_child_process4 = require("child_process");
19640
+ var import_util5 = require("util");
19641
+ var fsPromises = __toESM(require("fs/promises"), 1);
19642
+ var execFileAsync = (0, import_util5.promisify)(import_child_process4.execFile);
19643
+ function isProcessAlive(pid) {
19644
+ if (!Number.isInteger(pid) || pid <= 0) return false;
19645
+ try {
19646
+ process.kill(pid, 0);
19647
+ return true;
19648
+ } catch {
19649
+ return false;
19650
+ }
19651
+ }
19652
+ async function getProcessStartTime(pid) {
19653
+ if (!Number.isInteger(pid) || pid <= 0) return void 0;
19654
+ if (process.platform === "win32") {
19655
+ return getProcessStartTimeWindows(pid);
19656
+ } else if (process.platform === "darwin") {
19657
+ return getProcessStartTimeMacOS(pid);
19658
+ } else if (process.platform === "linux") {
19659
+ return getProcessStartTimeLinux(pid);
19660
+ }
19661
+ return void 0;
19662
+ }
19663
+ async function getProcessStartTimeWindows(pid) {
19664
+ try {
19665
+ const { stdout } = await execFileAsync("wmic", [
19666
+ "process",
19667
+ "where",
19668
+ `ProcessId=${pid}`,
19669
+ "get",
19670
+ "CreationDate",
19671
+ "/format:csv"
19672
+ ], { timeout: 5e3, windowsHide: true });
19673
+ const lines = stdout.trim().split(/\r?\n/).filter((l) => l.trim());
19674
+ if (lines.length < 2) return void 0;
19675
+ const match = lines[1].match(/,(\d{14})/);
19676
+ if (!match) return void 0;
19677
+ const d = match[1];
19678
+ const date3 = new Date(
19679
+ parseInt(d.slice(0, 4)),
19680
+ parseInt(d.slice(4, 6)) - 1,
19681
+ parseInt(d.slice(6, 8)),
19682
+ parseInt(d.slice(8, 10)),
19683
+ parseInt(d.slice(10, 12)),
19684
+ parseInt(d.slice(12, 14))
19685
+ );
19686
+ return date3.getTime();
19687
+ } catch {
19688
+ return void 0;
19689
+ }
19690
+ }
19691
+ async function getProcessStartTimeMacOS(pid) {
19692
+ try {
19693
+ const { stdout } = await execFileAsync("ps", ["-p", String(pid), "-o", "lstart="], {
19694
+ env: { ...process.env, LC_ALL: "C" },
19695
+ windowsHide: true
19696
+ });
19697
+ const date3 = new Date(stdout.trim());
19698
+ return isNaN(date3.getTime()) ? void 0 : date3.getTime();
19699
+ } catch {
19700
+ return void 0;
19701
+ }
19702
+ }
19703
+ async function getProcessStartTimeLinux(pid) {
19704
+ try {
19705
+ const stat = await fsPromises.readFile(`/proc/${pid}/stat`, "utf8");
19706
+ const closeParen = stat.lastIndexOf(")");
19707
+ if (closeParen === -1) return void 0;
19708
+ const fields = stat.substring(closeParen + 2).split(" ");
19709
+ const startTime = parseInt(fields[19], 10);
19710
+ return isNaN(startTime) ? void 0 : startTime;
19711
+ } catch {
19712
+ return void 0;
19713
+ }
19714
+ }
19715
+
19716
+ // src/platform/index.ts
19717
+ var PLATFORM = process.platform;
19718
+
19719
+ // src/tools/python-repl/session-lock.ts
19720
+ var execFileAsync2 = (0, import_util6.promisify)(import_child_process5.execFile);
19721
+ var STALE_LOCK_AGE_MS = 6e4;
19722
+ var DEFAULT_ACQUIRE_TIMEOUT_MS = 3e4;
19723
+ var LOCK_RETRY_INTERVAL_MS = 100;
19724
+ var REMOTE_LOCK_STALE_AGE_MS = 3e5;
19725
+ var LockTimeoutError = class extends Error {
19726
+ constructor(lockPath, timeout, lastHolder) {
19727
+ super(
19728
+ `Failed to acquire lock within ${timeout}ms. ` + (lastHolder ? `Held by PID ${lastHolder.pid} on ${lastHolder.hostname} since ${lastHolder.acquiredAt}` : "Unknown holder") + `. Lock path: ${lockPath}`
19729
+ );
19730
+ this.lockPath = lockPath;
19731
+ this.timeout = timeout;
19732
+ this.lastHolder = lastHolder;
19733
+ this.name = "LockTimeoutError";
19734
+ }
19735
+ };
19736
+ var LockError = class extends Error {
19737
+ constructor(message) {
19738
+ super(message);
19739
+ this.name = "LockError";
19740
+ }
19741
+ };
19742
+ function isValidPid(pid) {
19743
+ return typeof pid === "number" && Number.isInteger(pid) && pid > 0;
19744
+ }
19745
+ async function getCurrentProcessStartTime() {
19746
+ return getProcessStartTime(process.pid);
19747
+ }
19748
+ async function isProcessAlive2(pid, recordedStartTime) {
19749
+ if (!isValidPid(pid)) return false;
19750
+ if (process.platform === "linux") {
19751
+ const currentStartTime = await getProcessStartTime(pid);
19752
+ if (currentStartTime === void 0) return false;
19753
+ if (recordedStartTime !== void 0 && currentStartTime !== recordedStartTime) {
19754
+ return false;
19755
+ }
19756
+ return true;
19757
+ } else if (process.platform === "darwin") {
19758
+ try {
19759
+ const { stdout } = await execFileAsync2("ps", ["-p", String(pid), "-o", "pid="], {
19760
+ env: { ...process.env, LC_ALL: "C" }
19761
+ });
19762
+ if (stdout.trim() === "") return false;
19763
+ if (recordedStartTime !== void 0) {
19764
+ const currentStartTime = await getProcessStartTime(pid);
19765
+ if (currentStartTime === void 0) {
19766
+ return false;
19767
+ }
19768
+ if (currentStartTime !== recordedStartTime) {
19769
+ return false;
19770
+ }
19771
+ }
19772
+ return true;
19773
+ } catch {
19774
+ return false;
19775
+ }
19776
+ } else if (process.platform === "win32") {
19777
+ try {
19778
+ process.kill(pid, 0);
19779
+ if (recordedStartTime !== void 0) {
19780
+ const currentStartTime = await getProcessStartTime(pid);
19781
+ if (currentStartTime === void 0) {
19782
+ return false;
19783
+ }
19784
+ if (currentStartTime !== recordedStartTime) {
19785
+ return false;
19786
+ }
19787
+ }
19788
+ return true;
19789
+ } catch {
19790
+ return false;
19791
+ }
19792
+ }
19793
+ return true;
19794
+ }
19795
+ async function openNoFollow(filePath, flags, mode) {
19796
+ const O_NOFOLLOW = fsSync2.constants.O_NOFOLLOW ?? 0;
19797
+ const flagsWithNoFollow = flags | O_NOFOLLOW;
19798
+ try {
19799
+ return await fs3.open(filePath, flagsWithNoFollow, mode);
19800
+ } catch (err) {
19801
+ if (err.code === "ELOOP") {
19802
+ throw new LockError(`Lock file is a symlink: ${filePath}`);
19803
+ }
19804
+ throw err;
19805
+ }
19806
+ }
19807
+ async function readFileNoFollow(filePath) {
19808
+ try {
19809
+ const stat = await fs3.lstat(filePath);
19810
+ if (stat.isSymbolicLink()) {
19811
+ throw new LockError(`Lock file is a symlink: ${filePath}`);
19812
+ }
19813
+ } catch (err) {
19814
+ if (err.code === "ENOENT") {
19815
+ throw err;
19816
+ }
19817
+ if (err instanceof LockError) {
19818
+ throw err;
19819
+ }
19820
+ }
19821
+ return fs3.readFile(filePath, "utf8");
19822
+ }
19823
+ async function readLockFile(lockPath) {
19824
+ try {
19825
+ const content = await readFileNoFollow(lockPath);
19826
+ const lockInfo = JSON.parse(content);
19827
+ if (!lockInfo.lockId || !isValidPid(lockInfo.pid) || !lockInfo.hostname || !lockInfo.acquiredAt) {
19828
+ return null;
19829
+ }
19830
+ return lockInfo;
19831
+ } catch {
19832
+ return null;
19833
+ }
19834
+ }
19835
+ async function createLockInfo(lockId) {
19836
+ return {
19837
+ lockId,
19838
+ pid: process.pid,
19839
+ processStartTime: await getCurrentProcessStartTime(),
19840
+ hostname: os2.hostname(),
19841
+ acquiredAt: (/* @__PURE__ */ new Date()).toISOString()
19842
+ };
19843
+ }
19844
+ async function canBreakLock(lockInfo) {
19845
+ const age = Date.now() - new Date(lockInfo.acquiredAt).getTime();
19846
+ if (age < STALE_LOCK_AGE_MS) {
19847
+ return false;
19848
+ }
19849
+ if (lockInfo.hostname !== os2.hostname()) {
19850
+ return age > REMOTE_LOCK_STALE_AGE_MS;
19851
+ }
19852
+ const alive = await isProcessAlive2(lockInfo.pid, lockInfo.processStartTime);
19853
+ return !alive;
19854
+ }
19855
+ var SessionLock = class {
19856
+ lockPath;
19857
+ lockId;
19858
+ held = false;
19859
+ lockInfo = null;
19860
+ constructor(sessionId) {
19861
+ this.lockPath = getSessionLockPath(sessionId);
19862
+ this.lockId = crypto3.randomUUID();
19863
+ }
19864
+ /**
19865
+ * Acquire lock with timeout (default 30s).
19866
+ * Blocks until lock is acquired or timeout is reached.
19867
+ *
19868
+ * @param timeout - Maximum time to wait in milliseconds
19869
+ * @throws LockTimeoutError if lock cannot be acquired within timeout
19870
+ */
19871
+ async acquire(timeout = DEFAULT_ACQUIRE_TIMEOUT_MS) {
19872
+ if (this.held) {
19873
+ throw new LockError("Lock already held by this instance");
19874
+ }
19875
+ const startTime = Date.now();
19876
+ let lastHolder;
19877
+ while (Date.now() - startTime < timeout) {
19878
+ const result = await this.tryAcquire();
19879
+ if (result.acquired) {
19880
+ return;
19881
+ }
19882
+ if (result.holder) {
19883
+ lastHolder = result.holder;
19884
+ }
19885
+ await sleep(LOCK_RETRY_INTERVAL_MS);
19886
+ }
19887
+ throw new LockTimeoutError(this.lockPath, timeout, lastHolder);
19888
+ }
19889
+ /**
19890
+ * Try to acquire lock (non-blocking).
19891
+ * Returns immediately with result indicating success or failure.
19892
+ */
19893
+ async tryAcquire() {
19894
+ try {
19895
+ const existingLock = await readLockFile(this.lockPath);
19896
+ if (existingLock) {
19897
+ if (await canBreakLock(existingLock)) {
19898
+ try {
19899
+ await fs3.unlink(this.lockPath);
19900
+ } catch {
19901
+ }
19902
+ } else {
19903
+ return {
19904
+ acquired: false,
19905
+ reason: "held_by_other",
19906
+ holder: existingLock
19907
+ };
19908
+ }
19909
+ }
19910
+ const newLockInfo = await createLockInfo(this.lockId);
19911
+ try {
19912
+ ensureDirSync(path4.dirname(this.lockPath));
19913
+ const flags = fsSync2.constants.O_WRONLY | fsSync2.constants.O_CREAT | fsSync2.constants.O_EXCL;
19914
+ const lockFile = await openNoFollow(this.lockPath, flags, 420);
19915
+ try {
19916
+ await lockFile.writeFile(JSON.stringify(newLockInfo, null, 2), { encoding: "utf8" });
19917
+ await lockFile.sync();
19918
+ } finally {
19919
+ await lockFile.close();
19920
+ }
19921
+ } catch (err) {
19922
+ if (err.code === "EEXIST") {
19923
+ return {
19924
+ acquired: false,
19925
+ reason: "held_by_other"
19926
+ };
19927
+ }
19928
+ throw err;
19929
+ }
19930
+ const verifyLock = await readLockFile(this.lockPath);
19931
+ if (!verifyLock || verifyLock.lockId !== this.lockId) {
19932
+ return {
19933
+ acquired: false,
19934
+ reason: "error"
19935
+ };
19936
+ }
19937
+ this.held = true;
19938
+ this.lockInfo = newLockInfo;
19939
+ return {
19940
+ acquired: true,
19941
+ reason: existingLock ? "stale_broken" : "success"
19942
+ };
19943
+ } catch (err) {
19944
+ return {
19945
+ acquired: false,
19946
+ reason: "error"
19947
+ };
19948
+ }
19949
+ }
19950
+ /**
19951
+ * Release held lock.
19952
+ * Safe to call multiple times - subsequent calls are no-ops.
19953
+ */
19954
+ async release() {
19955
+ if (!this.held) {
19956
+ return;
19957
+ }
19958
+ try {
19959
+ const currentLock = await readLockFile(this.lockPath);
19960
+ if (currentLock && currentLock.lockId === this.lockId) {
19961
+ await fs3.unlink(this.lockPath);
19962
+ }
19963
+ } catch {
19964
+ } finally {
19965
+ this.held = false;
19966
+ this.lockInfo = null;
19967
+ }
19968
+ }
19969
+ /**
19970
+ * Force break a stale lock.
19971
+ * USE WITH CAUTION: This will break the lock regardless of who holds it.
19972
+ * Should only be used for recovery from known stale states.
19973
+ */
19974
+ async forceBreak() {
19975
+ try {
19976
+ await fs3.unlink(this.lockPath);
19977
+ } catch (err) {
19978
+ if (err.code !== "ENOENT") {
19979
+ throw err;
19980
+ }
19981
+ }
19982
+ this.held = false;
19983
+ this.lockInfo = null;
19984
+ }
19985
+ /**
19986
+ * Check if lock is held by us.
19987
+ */
19988
+ isHeld() {
19989
+ return this.held;
19990
+ }
19991
+ /**
19992
+ * Get the lock file path.
19993
+ */
19994
+ getLockPath() {
19995
+ return this.lockPath;
19996
+ }
19997
+ /**
19998
+ * Get current lock info (if held).
19999
+ */
20000
+ getLockInfo() {
20001
+ return this.lockInfo;
20002
+ }
20003
+ };
20004
+ function sleep(ms) {
20005
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
20006
+ }
20007
+
20008
+ // src/tools/python-repl/socket-client.ts
20009
+ var net = __toESM(require("net"), 1);
20010
+ var import_crypto = require("crypto");
20011
+ var SocketConnectionError = class extends Error {
20012
+ constructor(message, socketPath, originalError) {
20013
+ super(message);
20014
+ this.socketPath = socketPath;
20015
+ this.originalError = originalError;
20016
+ this.name = "SocketConnectionError";
20017
+ }
20018
+ };
20019
+ var SocketTimeoutError = class extends Error {
20020
+ constructor(message, timeoutMs) {
20021
+ super(message);
20022
+ this.timeoutMs = timeoutMs;
20023
+ this.name = "SocketTimeoutError";
20024
+ }
20025
+ };
20026
+ var JsonRpcError = class extends Error {
20027
+ constructor(message, code, data) {
20028
+ super(message);
20029
+ this.code = code;
20030
+ this.data = data;
20031
+ this.name = "JsonRpcError";
20032
+ }
20033
+ };
20034
+ async function sendSocketRequest(socketPath, method, params, timeout = 6e4) {
20035
+ return new Promise((resolve4, reject) => {
20036
+ const id = (0, import_crypto.randomUUID)();
20037
+ const request = {
20038
+ jsonrpc: "2.0",
20039
+ id,
20040
+ method,
20041
+ params: params ?? {}
20042
+ };
20043
+ const requestLine = JSON.stringify(request) + "\n";
20044
+ let responseBuffer = "";
20045
+ let timedOut = false;
20046
+ const MAX_RESPONSE_SIZE = 2 * 1024 * 1024;
20047
+ const timer = setTimeout(() => {
20048
+ timedOut = true;
20049
+ socket.destroy();
20050
+ reject(new SocketTimeoutError(
20051
+ `Request timeout after ${timeout}ms for method "${method}"`,
20052
+ timeout
20053
+ ));
20054
+ }, timeout);
20055
+ const cleanup = () => {
20056
+ clearTimeout(timer);
20057
+ socket.removeAllListeners();
20058
+ socket.destroy();
20059
+ };
20060
+ const socket = net.createConnection({ path: socketPath });
20061
+ socket.on("connect", () => {
20062
+ socket.write(requestLine);
20063
+ });
20064
+ socket.on("data", (chunk) => {
20065
+ responseBuffer += chunk.toString();
20066
+ if (responseBuffer.length > MAX_RESPONSE_SIZE) {
20067
+ cleanup();
20068
+ reject(new Error(
20069
+ `Response exceeded maximum size of ${MAX_RESPONSE_SIZE} bytes`
20070
+ ));
20071
+ return;
20072
+ }
20073
+ const newlineIndex = responseBuffer.indexOf("\n");
20074
+ if (newlineIndex !== -1) {
20075
+ const jsonLine = responseBuffer.slice(0, newlineIndex);
20076
+ cleanup();
20077
+ try {
20078
+ const response = JSON.parse(jsonLine);
20079
+ if (response.jsonrpc !== "2.0") {
20080
+ reject(new Error(
20081
+ `Invalid JSON-RPC version: expected "2.0", got "${response.jsonrpc}"`
20082
+ ));
20083
+ return;
20084
+ }
20085
+ if (response.id !== id) {
20086
+ reject(new Error(
20087
+ `Response ID mismatch: expected "${id}", got "${response.id}"`
20088
+ ));
20089
+ return;
20090
+ }
20091
+ if (response.error) {
20092
+ reject(new JsonRpcError(
20093
+ response.error.message,
20094
+ response.error.code,
20095
+ response.error.data
20096
+ ));
20097
+ return;
20098
+ }
20099
+ resolve4(response.result);
20100
+ } catch (e) {
20101
+ reject(new Error(
20102
+ `Failed to parse JSON-RPC response: ${e.message}`
20103
+ ));
20104
+ }
20105
+ }
20106
+ });
20107
+ socket.on("error", (err) => {
20108
+ if (timedOut) {
20109
+ return;
20110
+ }
20111
+ cleanup();
20112
+ if (err.code === "ENOENT") {
20113
+ reject(new SocketConnectionError(
20114
+ `Socket does not exist at path: ${socketPath}`,
20115
+ socketPath,
20116
+ err
20117
+ ));
20118
+ } else if (err.code === "ECONNREFUSED") {
20119
+ reject(new SocketConnectionError(
20120
+ `Connection refused - server not listening at: ${socketPath}`,
20121
+ socketPath,
20122
+ err
20123
+ ));
20124
+ } else {
20125
+ reject(new SocketConnectionError(
20126
+ `Socket connection error: ${err.message}`,
20127
+ socketPath,
20128
+ err
20129
+ ));
20130
+ }
20131
+ });
20132
+ socket.on("close", () => {
20133
+ if (timedOut) {
20134
+ return;
20135
+ }
20136
+ if (responseBuffer.indexOf("\n") === -1) {
20137
+ cleanup();
20138
+ reject(new Error(
20139
+ `Socket closed without sending complete response (method: "${method}")`
20140
+ ));
20141
+ }
20142
+ });
20143
+ });
20144
+ }
20145
+
20146
+ // src/tools/python-repl/bridge-manager.ts
20147
+ var import_child_process6 = require("child_process");
20148
+ var fs4 = __toESM(require("fs"), 1);
20149
+ var fsPromises2 = __toESM(require("fs/promises"), 1);
20150
+ var path5 = __toESM(require("path"), 1);
20151
+ var import_url = require("url");
20152
+ var import_child_process7 = require("child_process");
20153
+ var import_util7 = require("util");
20154
+ var import_meta = {};
20155
+ var execFileAsync3 = (0, import_util7.promisify)(import_child_process7.execFile);
20156
+ var BRIDGE_SPAWN_TIMEOUT_MS = 3e4;
20157
+ var DEFAULT_GRACE_PERIOD_MS = 5e3;
20158
+ var SIGTERM_GRACE_MS = 2500;
20159
+ function getBridgeScriptPath() {
20160
+ const __filename = (0, import_url.fileURLToPath)(import_meta.url);
20161
+ const __dirname = path5.dirname(__filename);
20162
+ const packageRoot = path5.resolve(__dirname, "..", "..", "..");
20163
+ return path5.join(packageRoot, "bridge", "gyoshu_bridge.py");
20164
+ }
20165
+ function detectExistingPythonEnv(projectRoot) {
20166
+ const isWindows = process.platform === "win32";
20167
+ const binDir = isWindows ? "Scripts" : "bin";
20168
+ const pythonExe = isWindows ? "python.exe" : "python";
20169
+ const venvPython = path5.join(projectRoot, ".venv", binDir, pythonExe);
20170
+ if (fs4.existsSync(venvPython)) {
20171
+ return { pythonPath: venvPython, type: "venv" };
20172
+ }
20173
+ return null;
20174
+ }
20175
+ async function ensurePythonEnvironment(projectRoot) {
20176
+ const existing = detectExistingPythonEnv(projectRoot);
20177
+ if (existing) {
20178
+ return existing;
20179
+ }
20180
+ try {
20181
+ await execFileAsync3("python3", ["--version"]);
20182
+ return { pythonPath: "python3", type: "venv" };
20183
+ } catch {
20184
+ }
20185
+ throw new Error(
20186
+ "No Python environment found. Create a virtual environment first:\n python -m venv .venv\n .venv/bin/pip install pandas numpy matplotlib"
20187
+ );
20188
+ }
20189
+ async function verifyProcessIdentity(meta) {
20190
+ if (!isProcessAlive(meta.pid)) {
20191
+ return false;
20192
+ }
20193
+ if (meta.processStartTime !== void 0) {
20194
+ const currentStartTime = await getProcessStartTime(meta.pid);
20195
+ if (currentStartTime === void 0) {
20196
+ return false;
20197
+ }
20198
+ if (currentStartTime !== meta.processStartTime) {
20199
+ return false;
20200
+ }
20201
+ }
20202
+ return true;
20203
+ }
20204
+ function isSocket(socketPath) {
20205
+ try {
20206
+ const stat = fs4.lstatSync(socketPath);
20207
+ return stat.isSocket();
20208
+ } catch {
20209
+ return false;
20210
+ }
20211
+ }
20212
+ function safeUnlinkSocket(socketPath) {
20213
+ try {
20214
+ if (fs4.existsSync(socketPath)) {
20215
+ fs4.unlinkSync(socketPath);
20216
+ }
20217
+ } catch {
20218
+ }
20219
+ }
20220
+ function isValidBridgeMeta(data) {
20221
+ if (typeof data !== "object" || data === null) return false;
20222
+ const obj = data;
20223
+ return typeof obj.pid === "number" && Number.isInteger(obj.pid) && obj.pid > 0 && typeof obj.socketPath === "string" && typeof obj.startedAt === "string" && typeof obj.sessionId === "string" && typeof obj.pythonEnv === "object" && obj.pythonEnv !== null && typeof obj.pythonEnv.pythonPath === "string" && (obj.processStartTime === void 0 || typeof obj.processStartTime === "number");
20224
+ }
20225
+ function killProcessGroup(pid, signal) {
20226
+ if (process.platform === "win32") {
20227
+ try {
20228
+ const force = signal === "SIGKILL";
20229
+ const args = force ? "/F /T" : "/T";
20230
+ require("child_process").execSync(
20231
+ `taskkill ${args} /PID ${pid}`,
20232
+ { stdio: "ignore", timeout: 5e3, windowsHide: true }
20233
+ );
20234
+ return true;
20235
+ } catch {
20236
+ return false;
20237
+ }
20238
+ } else {
20239
+ try {
20240
+ process.kill(-pid, signal);
20241
+ return true;
20242
+ } catch {
20243
+ try {
20244
+ process.kill(pid, signal);
20245
+ return true;
20246
+ } catch {
20247
+ return false;
20248
+ }
20249
+ }
20250
+ }
20251
+ }
20252
+ async function spawnBridgeServer(sessionId, projectDir) {
20253
+ const sessionDir = getSessionDir(sessionId);
20254
+ ensureDirSync(sessionDir);
20255
+ const socketPath = getBridgeSocketPath(sessionId);
20256
+ const bridgePath = getBridgeScriptPath();
20257
+ if (!fs4.existsSync(bridgePath)) {
20258
+ throw new Error(`Bridge script not found: ${bridgePath}`);
20259
+ }
20260
+ safeUnlinkSocket(socketPath);
20261
+ const effectiveProjectDir = projectDir || process.cwd();
20262
+ const pythonEnv = await ensurePythonEnvironment(effectiveProjectDir);
20263
+ const bridgeArgs = [bridgePath, socketPath];
20264
+ const proc = (0, import_child_process6.spawn)(pythonEnv.pythonPath, bridgeArgs, {
20265
+ stdio: ["ignore", "ignore", "pipe"],
20266
+ cwd: effectiveProjectDir,
20267
+ env: { ...process.env, PYTHONUNBUFFERED: "1" },
20268
+ detached: true
20269
+ });
20270
+ proc.unref();
20271
+ const MAX_STDERR_CHARS = 64 * 1024;
20272
+ let stderrBuffer = "";
20273
+ let stderrTruncated = false;
20274
+ proc.stderr?.on("data", (chunk) => {
20275
+ if (stderrTruncated) return;
20276
+ const text = chunk.toString();
20277
+ if (stderrBuffer.length + text.length > MAX_STDERR_CHARS) {
20278
+ stderrBuffer = stderrBuffer.slice(0, MAX_STDERR_CHARS - 20) + "\n...[truncated]";
20279
+ stderrTruncated = true;
20280
+ } else {
20281
+ stderrBuffer += text;
20282
+ }
20283
+ });
20284
+ const startTime = Date.now();
20285
+ while (!isSocket(socketPath)) {
20286
+ if (Date.now() - startTime > BRIDGE_SPAWN_TIMEOUT_MS) {
20287
+ if (proc.pid) {
20288
+ killProcessGroup(proc.pid, "SIGKILL");
20289
+ }
20290
+ if (fs4.existsSync(socketPath) && !isSocket(socketPath)) {
20291
+ safeUnlinkSocket(socketPath);
20292
+ }
20293
+ throw new Error(
20294
+ `Bridge failed to create socket in ${BRIDGE_SPAWN_TIMEOUT_MS}ms. Stderr: ${stderrBuffer || "(empty)"}`
20295
+ );
20296
+ }
20297
+ await sleep2(100);
20298
+ }
20299
+ const processStartTime = proc.pid ? await getProcessStartTime(proc.pid) : void 0;
20300
+ const meta = {
20301
+ pid: proc.pid,
20302
+ socketPath,
20303
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
20304
+ sessionId,
20305
+ pythonEnv,
20306
+ processStartTime
20307
+ };
20308
+ const metaPath = getBridgeMetaPath(sessionId);
20309
+ await atomicWriteJson(metaPath, meta);
20310
+ return meta;
20311
+ }
20312
+ async function ensureBridge(sessionId, projectDir) {
20313
+ const metaPath = getBridgeMetaPath(sessionId);
20314
+ const expectedSocketPath = getBridgeSocketPath(sessionId);
20315
+ const meta = await safeReadJson(metaPath);
20316
+ if (meta && isValidBridgeMeta(meta)) {
20317
+ if (meta.sessionId !== sessionId) {
20318
+ await deleteBridgeMeta(sessionId);
20319
+ return spawnBridgeServer(sessionId, projectDir);
20320
+ }
20321
+ if (meta.socketPath !== expectedSocketPath) {
20322
+ await deleteBridgeMeta(sessionId);
20323
+ return spawnBridgeServer(sessionId, projectDir);
20324
+ }
20325
+ const stillOurs = await verifyProcessIdentity(meta);
20326
+ if (stillOurs) {
20327
+ if (isSocket(meta.socketPath)) {
20328
+ return meta;
20329
+ } else {
20330
+ try {
20331
+ process.kill(meta.pid, "SIGKILL");
20332
+ } catch {
20333
+ }
20334
+ }
20335
+ }
20336
+ await deleteBridgeMeta(sessionId);
20337
+ }
20338
+ return spawnBridgeServer(sessionId, projectDir);
20339
+ }
20340
+ async function killBridgeWithEscalation(sessionId, options) {
20341
+ const gracePeriod = options?.gracePeriodMs ?? DEFAULT_GRACE_PERIOD_MS;
20342
+ const startTime = Date.now();
20343
+ const metaPath = getBridgeMetaPath(sessionId);
20344
+ const meta = await safeReadJson(metaPath);
20345
+ if (!meta || !isValidBridgeMeta(meta)) {
20346
+ return { terminated: true };
20347
+ }
20348
+ if (meta.sessionId !== sessionId) {
20349
+ await deleteBridgeMeta(sessionId);
20350
+ return { terminated: true };
20351
+ }
20352
+ if (!await verifyProcessIdentity(meta)) {
20353
+ await deleteBridgeMeta(sessionId);
20354
+ return { terminated: true };
20355
+ }
20356
+ const waitForExit = async (timeoutMs) => {
20357
+ const checkStart = Date.now();
20358
+ while (Date.now() - checkStart < timeoutMs) {
20359
+ const stillOurs = await verifyProcessIdentity(meta);
20360
+ if (!stillOurs) {
20361
+ return true;
20362
+ }
20363
+ await sleep2(100);
20364
+ }
20365
+ return false;
20366
+ };
20367
+ let terminatedBy = "SIGINT";
20368
+ killProcessGroup(meta.pid, "SIGINT");
20369
+ if (!await waitForExit(gracePeriod)) {
20370
+ terminatedBy = "SIGTERM";
20371
+ killProcessGroup(meta.pid, "SIGTERM");
20372
+ if (!await waitForExit(SIGTERM_GRACE_MS)) {
20373
+ terminatedBy = "SIGKILL";
20374
+ killProcessGroup(meta.pid, "SIGKILL");
20375
+ await waitForExit(1e3);
20376
+ }
20377
+ }
20378
+ await deleteBridgeMeta(sessionId);
20379
+ const sessionDir = getSessionDir(sessionId);
20380
+ const socketPath = meta.socketPath;
20381
+ if (socketPath.startsWith(sessionDir)) {
20382
+ safeUnlinkSocket(socketPath);
20383
+ }
20384
+ return {
20385
+ terminated: true,
20386
+ terminatedBy,
20387
+ terminationTimeMs: Date.now() - startTime
20388
+ };
20389
+ }
20390
+ async function deleteBridgeMeta(sessionId) {
20391
+ const metaPath = getBridgeMetaPath(sessionId);
20392
+ try {
20393
+ await fsPromises2.unlink(metaPath);
20394
+ } catch {
20395
+ }
20396
+ }
20397
+ function sleep2(ms) {
20398
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
20399
+ }
20400
+
20401
+ // src/tools/python-repl/tool.ts
20402
+ var DEFAULT_EXECUTION_TIMEOUT_MS = 3e5;
20403
+ var DEFAULT_QUEUE_TIMEOUT_MS = 3e4;
20404
+ var pythonReplSchema = external_exports.object({
20405
+ action: external_exports.enum(["execute", "interrupt", "reset", "get_state"]).describe(
20406
+ "Action to perform: execute (run Python code), interrupt (stop running code), reset (clear namespace), get_state (memory and variables)"
20407
+ ),
20408
+ researchSessionID: external_exports.string().min(1, "researchSessionID is required").describe("Unique identifier for the research session"),
20409
+ code: external_exports.string().optional().describe('Python code to execute (required for "execute" action)'),
20410
+ executionLabel: external_exports.string().optional().describe(
20411
+ 'Human-readable label for this code execution. Examples: "Load dataset", "Train model", "Generate plot"'
20412
+ ),
20413
+ executionTimeout: external_exports.number().positive().default(DEFAULT_EXECUTION_TIMEOUT_MS).describe("Timeout for code execution in milliseconds (default: 300000 = 5 min)"),
20414
+ queueTimeout: external_exports.number().positive().default(DEFAULT_QUEUE_TIMEOUT_MS).describe("Timeout for acquiring session lock in milliseconds (default: 30000 = 30 sec)"),
20415
+ projectDir: external_exports.string().optional().describe("Project directory containing .venv/. Defaults to current working directory.")
20416
+ });
20417
+ var executionCounters = /* @__PURE__ */ new Map();
20418
+ function getNextExecutionCount(sessionId) {
20419
+ const current = executionCounters.get(sessionId) || 0;
20420
+ const next = current + 1;
20421
+ executionCounters.set(sessionId, next);
20422
+ return next;
20423
+ }
20424
+ function formatExecuteResult(result, sessionId, executionLabel, executionCount) {
20425
+ const lines = [];
20426
+ lines.push("=== Python REPL Execution ===");
20427
+ lines.push(`Session: ${sessionId}`);
20428
+ if (executionLabel) {
20429
+ lines.push(`Label: ${executionLabel}`);
20430
+ }
20431
+ if (executionCount !== void 0) {
20432
+ lines.push(`Execution #: ${executionCount}`);
20433
+ }
20434
+ lines.push("");
20435
+ if (result.stdout) {
20436
+ lines.push("--- Output ---");
20437
+ lines.push(result.stdout.trimEnd());
20438
+ lines.push("");
20439
+ }
20440
+ if (result.stderr) {
20441
+ lines.push("--- Errors ---");
20442
+ lines.push(result.stderr.trimEnd());
20443
+ lines.push("");
20444
+ }
20445
+ if (result.markers && result.markers.length > 0) {
20446
+ lines.push("--- Markers ---");
20447
+ for (const marker of result.markers) {
20448
+ const subtypeStr = marker.subtype ? `:${marker.subtype}` : "";
20449
+ lines.push(`[${marker.type}${subtypeStr}] ${marker.content}`);
20450
+ }
20451
+ lines.push("");
20452
+ }
20453
+ if (result.timing) {
20454
+ lines.push("--- Timing ---");
20455
+ const durationSec = (result.timing.duration_ms / 1e3).toFixed(3);
20456
+ lines.push(`Duration: ${durationSec}s`);
20457
+ lines.push(`Started: ${result.timing.started_at}`);
20458
+ lines.push("");
20459
+ }
20460
+ if (result.memory) {
20461
+ lines.push("--- Memory ---");
20462
+ lines.push(`RSS: ${result.memory.rss_mb.toFixed(1)} MB`);
20463
+ lines.push(`VMS: ${result.memory.vms_mb.toFixed(1)} MB`);
20464
+ lines.push("");
20465
+ }
20466
+ if (result.error) {
20467
+ lines.push("=== Execution Failed ===");
20468
+ lines.push(`Error Type: ${result.error.type}`);
20469
+ lines.push(`Message: ${result.error.message}`);
20470
+ if (result.error.traceback) {
20471
+ lines.push("");
20472
+ lines.push("Traceback:");
20473
+ lines.push(result.error.traceback);
20474
+ }
20475
+ lines.push("");
20476
+ }
20477
+ lines.push(result.success ? "=== Execution Complete ===" : "=== Execution Failed ===");
20478
+ return lines.join("\n");
20479
+ }
20480
+ function formatStateResult(result, sessionId) {
20481
+ const lines = [];
20482
+ lines.push("=== Python REPL State ===");
20483
+ lines.push(`Session: ${sessionId}`);
20484
+ lines.push("");
20485
+ lines.push("--- Memory ---");
20486
+ lines.push(`RSS: ${result.memory.rss_mb.toFixed(1)} MB`);
20487
+ lines.push(`VMS: ${result.memory.vms_mb.toFixed(1)} MB`);
20488
+ lines.push("");
20489
+ lines.push("--- Variables ---");
20490
+ lines.push(`Count: ${result.variable_count}`);
20491
+ if (result.variables.length > 0) {
20492
+ lines.push("");
20493
+ const chunks = [];
20494
+ for (let i = 0; i < result.variables.length; i += 10) {
20495
+ chunks.push(result.variables.slice(i, i + 10));
20496
+ }
20497
+ for (const chunk of chunks) {
20498
+ lines.push(chunk.join(", "));
20499
+ }
20500
+ } else {
20501
+ lines.push("(no user variables defined)");
20502
+ }
20503
+ lines.push("");
20504
+ lines.push("=== State Retrieved ===");
20505
+ return lines.join("\n");
20506
+ }
20507
+ function formatResetResult(result, sessionId) {
20508
+ const lines = [];
20509
+ lines.push("=== Python REPL Reset ===");
20510
+ lines.push(`Session: ${sessionId}`);
20511
+ lines.push(`Status: ${result.status}`);
20512
+ lines.push("");
20513
+ lines.push("--- Memory After Reset ---");
20514
+ lines.push(`RSS: ${result.memory.rss_mb.toFixed(1)} MB`);
20515
+ lines.push(`VMS: ${result.memory.vms_mb.toFixed(1)} MB`);
20516
+ lines.push("");
20517
+ lines.push("=== Namespace Cleared ===");
20518
+ return lines.join("\n");
20519
+ }
20520
+ function formatInterruptResult(result, sessionId) {
20521
+ const lines = [];
20522
+ lines.push("=== Python REPL Interrupt ===");
20523
+ lines.push(`Session: ${sessionId}`);
20524
+ lines.push(`Status: ${result.status}`);
20525
+ if (result.terminatedBy) {
20526
+ lines.push(`Terminated By: ${result.terminatedBy}`);
20527
+ }
20528
+ if (result.terminationTimeMs !== void 0) {
20529
+ lines.push(`Termination Time: ${result.terminationTimeMs}ms`);
20530
+ }
20531
+ lines.push("");
20532
+ lines.push("=== Execution Interrupted ===");
20533
+ return lines.join("\n");
20534
+ }
20535
+ function formatLockTimeoutError(error2, sessionId) {
20536
+ const lines = [];
20537
+ lines.push("=== Session Busy ===");
20538
+ lines.push(`Session: ${sessionId}`);
20539
+ lines.push("");
20540
+ lines.push("The session is currently busy processing another request.");
20541
+ lines.push(`Queue timeout: ${error2.timeout}ms`);
20542
+ lines.push("");
20543
+ if (error2.lastHolder) {
20544
+ lines.push("Current holder:");
20545
+ lines.push(` PID: ${error2.lastHolder.pid}`);
20546
+ lines.push(` Host: ${error2.lastHolder.hostname}`);
20547
+ lines.push(` Since: ${error2.lastHolder.acquiredAt}`);
20548
+ lines.push("");
20549
+ }
20550
+ lines.push("Suggestions:");
20551
+ lines.push(" 1. Wait and retry later");
20552
+ lines.push(' 2. Use the "interrupt" action to stop the current execution');
20553
+ lines.push(' 3. Use the "reset" action to clear the session');
20554
+ return lines.join("\n");
20555
+ }
20556
+ function formatSocketError(error2, sessionId) {
20557
+ const lines = [];
20558
+ lines.push("=== Connection Error ===");
20559
+ lines.push(`Session: ${sessionId}`);
20560
+ lines.push("");
20561
+ lines.push(`Error: ${error2.message}`);
20562
+ lines.push(`Socket: ${error2.socketPath}`);
20563
+ lines.push("");
20564
+ lines.push("Troubleshooting:");
20565
+ lines.push(" 1. The bridge process may have crashed - retry will auto-restart");
20566
+ lines.push(' 2. Use "reset" action to force restart the bridge');
20567
+ lines.push(" 3. Ensure .venv exists with Python installed");
20568
+ return lines.join("\n");
20569
+ }
20570
+ function formatGeneralError(error2, sessionId, action) {
20571
+ const lines = [];
20572
+ lines.push("=== Error ===");
20573
+ lines.push(`Session: ${sessionId}`);
20574
+ lines.push(`Action: ${action}`);
20575
+ lines.push("");
20576
+ lines.push(`Type: ${error2.name}`);
20577
+ lines.push(`Message: ${error2.message}`);
20578
+ if (error2.stack) {
20579
+ lines.push("");
20580
+ lines.push("Stack trace:");
20581
+ lines.push(error2.stack);
20582
+ }
20583
+ return lines.join("\n");
20584
+ }
20585
+ async function handleExecute(sessionId, socketPath, code, executionTimeout, executionLabel) {
20586
+ const executionCount = getNextExecutionCount(sessionId);
20587
+ try {
20588
+ const result = await sendSocketRequest(
20589
+ socketPath,
20590
+ "execute",
20591
+ { code, timeout: executionTimeout / 1e3 },
20592
+ executionTimeout + 1e4
20593
+ // Allow extra time for response
20594
+ );
20595
+ return formatExecuteResult(result, sessionId, executionLabel, executionCount);
20596
+ } catch (error2) {
20597
+ if (error2 instanceof SocketConnectionError) {
20598
+ throw error2;
20599
+ }
20600
+ if (error2 instanceof SocketTimeoutError) {
20601
+ return [
20602
+ "=== Execution Timeout ===",
20603
+ `Session: ${sessionId}`,
20604
+ `Label: ${executionLabel || "(none)"}`,
20605
+ "",
20606
+ `The code execution exceeded the timeout of ${executionTimeout / 1e3} seconds.`,
20607
+ "",
20608
+ "The execution is still running in the background.",
20609
+ 'Use the "interrupt" action to stop it.'
20610
+ ].join("\n");
20611
+ }
20612
+ if (error2 instanceof JsonRpcError) {
20613
+ return [
20614
+ "=== Execution Failed ===",
20615
+ `Session: ${sessionId}`,
20616
+ "",
20617
+ `Error Code: ${error2.code}`,
20618
+ `Message: ${error2.message}`,
20619
+ error2.data ? `Data: ${JSON.stringify(error2.data, null, 2)}` : ""
20620
+ ].filter(Boolean).join("\n");
20621
+ }
20622
+ throw error2;
20623
+ }
20624
+ }
20625
+ async function handleReset(sessionId, socketPath) {
20626
+ try {
20627
+ const result = await sendSocketRequest(socketPath, "reset", {}, 1e4);
20628
+ return formatResetResult(result, sessionId);
20629
+ } catch (error2) {
20630
+ await killBridgeWithEscalation(sessionId);
20631
+ return [
20632
+ "=== Bridge Restarted ===",
20633
+ `Session: ${sessionId}`,
20634
+ "",
20635
+ "The bridge was unresponsive and has been terminated.",
20636
+ "A new bridge will be spawned on the next request.",
20637
+ "",
20638
+ "Memory has been cleared."
20639
+ ].join("\n");
20640
+ }
20641
+ }
20642
+ async function handleGetState(sessionId, socketPath) {
20643
+ try {
20644
+ const result = await sendSocketRequest(socketPath, "get_state", {}, 5e3);
20645
+ return formatStateResult(result, sessionId);
20646
+ } catch (error2) {
20647
+ if (error2 instanceof SocketConnectionError) {
20648
+ throw error2;
20649
+ }
20650
+ if (error2 instanceof SocketTimeoutError) {
20651
+ return [
20652
+ "=== State Retrieval Timeout ===",
20653
+ `Session: ${sessionId}`,
20654
+ "",
20655
+ "Could not retrieve state within timeout.",
20656
+ "The bridge may be busy with a long-running execution."
20657
+ ].join("\n");
20658
+ }
20659
+ throw error2;
20660
+ }
20661
+ }
20662
+ async function handleInterrupt(sessionId, socketPath, gracePeriodMs = 5e3) {
20663
+ try {
20664
+ const result = await sendSocketRequest(
20665
+ socketPath,
20666
+ "interrupt",
20667
+ {},
20668
+ Math.min(gracePeriodMs, 5e3)
20669
+ );
20670
+ return formatInterruptResult(
20671
+ {
20672
+ ...result,
20673
+ status: result.status || "interrupted",
20674
+ terminatedBy: "graceful"
20675
+ },
20676
+ sessionId
20677
+ );
20678
+ } catch {
20679
+ const escalationResult = await killBridgeWithEscalation(sessionId, { gracePeriodMs });
20680
+ return formatInterruptResult(
20681
+ {
20682
+ status: "force_killed",
20683
+ terminatedBy: escalationResult.terminatedBy,
20684
+ terminationTimeMs: escalationResult.terminationTimeMs
20685
+ },
20686
+ sessionId
20687
+ );
20688
+ }
20689
+ }
20690
+ async function pythonReplHandler(input) {
20691
+ const parseResult = pythonReplSchema.safeParse(input);
20692
+ if (!parseResult.success) {
20693
+ const errors = parseResult.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`);
20694
+ return [
20695
+ "=== Validation Error ===",
20696
+ "",
20697
+ "Invalid input parameters:",
20698
+ ...errors.map((e) => ` - ${e}`)
20699
+ ].join("\n");
20700
+ }
20701
+ const {
20702
+ action,
20703
+ researchSessionID: sessionId,
20704
+ code,
20705
+ executionLabel,
20706
+ executionTimeout,
20707
+ queueTimeout,
20708
+ projectDir
20709
+ } = parseResult.data;
20710
+ try {
20711
+ validatePathSegment(sessionId, "researchSessionID");
20712
+ } catch (error2) {
20713
+ return [
20714
+ "=== Invalid Session ID ===",
20715
+ "",
20716
+ `Error: ${error2.message}`,
20717
+ "",
20718
+ "Session IDs must be safe path segments without:",
20719
+ " - Path separators (/ or \\)",
20720
+ " - Parent directory references (..)",
20721
+ " - Null bytes",
20722
+ " - Windows reserved names (CON, PRN, etc.)"
20723
+ ].join("\n");
20724
+ }
20725
+ if (action === "execute" && !code) {
20726
+ return [
20727
+ "=== Missing Code ===",
20728
+ "",
20729
+ 'The "execute" action requires the "code" parameter.',
20730
+ "",
20731
+ "Example:",
20732
+ ' action: "execute"',
20733
+ ` code: "print('Hello!')"`
20734
+ ].join("\n");
20735
+ }
20736
+ const lock = new SessionLock(sessionId);
20737
+ try {
20738
+ await lock.acquire(queueTimeout);
20739
+ } catch (error2) {
20740
+ if (error2 instanceof LockTimeoutError) {
20741
+ return formatLockTimeoutError(error2, sessionId);
20742
+ }
20743
+ return formatGeneralError(error2, sessionId, action);
20744
+ }
20745
+ try {
20746
+ let meta;
20747
+ try {
20748
+ meta = await ensureBridge(sessionId, projectDir);
20749
+ } catch (error2) {
20750
+ return [
20751
+ "=== Bridge Startup Failed ===",
20752
+ `Session: ${sessionId}`,
20753
+ "",
20754
+ `Error: ${error2.message}`,
20755
+ "",
20756
+ "Ensure you have a Python virtual environment:",
20757
+ " python -m venv .venv",
20758
+ " .venv/bin/pip install pandas numpy matplotlib"
20759
+ ].join("\n");
20760
+ }
20761
+ switch (action) {
20762
+ case "execute":
20763
+ try {
20764
+ return await handleExecute(
20765
+ sessionId,
20766
+ meta.socketPath,
20767
+ code,
20768
+ executionTimeout,
20769
+ executionLabel
20770
+ );
20771
+ } catch (error2) {
20772
+ if (error2 instanceof SocketConnectionError) {
20773
+ try {
20774
+ meta = await spawnBridgeServer(sessionId, projectDir);
20775
+ return await handleExecute(
20776
+ sessionId,
20777
+ meta.socketPath,
20778
+ code,
20779
+ executionTimeout,
20780
+ executionLabel
20781
+ );
20782
+ } catch (retryError) {
20783
+ return formatSocketError(
20784
+ retryError instanceof SocketConnectionError ? retryError : new SocketConnectionError(retryError.message, meta.socketPath),
20785
+ sessionId
20786
+ );
20787
+ }
20788
+ }
20789
+ return formatGeneralError(error2, sessionId, action);
20790
+ }
20791
+ case "reset":
20792
+ return await handleReset(sessionId, meta.socketPath);
20793
+ case "get_state":
20794
+ try {
20795
+ return await handleGetState(sessionId, meta.socketPath);
20796
+ } catch (error2) {
20797
+ if (error2 instanceof SocketConnectionError) {
20798
+ return formatSocketError(error2, sessionId);
20799
+ }
20800
+ return formatGeneralError(error2, sessionId, action);
20801
+ }
20802
+ case "interrupt":
20803
+ return await handleInterrupt(sessionId, meta.socketPath);
20804
+ default:
20805
+ return [
20806
+ "=== Unknown Action ===",
20807
+ "",
20808
+ `Received action: ${action}`,
20809
+ "",
20810
+ "Valid actions are:",
20811
+ " - execute: Run Python code",
20812
+ " - interrupt: Stop running code",
20813
+ " - reset: Clear the namespace",
20814
+ " - get_state: Get memory and variable info"
20815
+ ].join("\n");
20816
+ }
20817
+ } finally {
20818
+ await lock.release();
20819
+ }
20820
+ }
20821
+ var pythonReplTool = {
20822
+ name: "python_repl",
20823
+ description: "Execute Python code in a persistent REPL environment. Variables and state persist between calls within the same session. Actions: execute (run code), interrupt (stop execution), reset (clear state), get_state (view memory/variables). Supports scientific computing with pandas, numpy, matplotlib.",
20824
+ schema: pythonReplSchema.shape,
20825
+ handler: async (args) => {
20826
+ const output = await pythonReplHandler(args);
20827
+ return {
20828
+ content: [{ type: "text", text: output }]
20829
+ };
20830
+ }
20831
+ };
20832
+
19029
20833
  // src/mcp/standalone-server.ts
20834
+ var allTools = [
20835
+ ...lspTools,
20836
+ ...astTools,
20837
+ pythonReplTool
20838
+ ];
19030
20839
  function zodToJsonSchema2(schema) {
19031
20840
  const rawShape = schema instanceof external_exports.ZodObject ? schema.shape : schema;
19032
20841
  const properties = {};
@@ -19094,7 +20903,7 @@ var server = new Server(
19094
20903
  );
19095
20904
  server.setRequestHandler(ListToolsRequestSchema, async () => {
19096
20905
  return {
19097
- tools: lspTools.map((tool) => ({
20906
+ tools: allTools.map((tool) => ({
19098
20907
  name: tool.name,
19099
20908
  description: tool.description,
19100
20909
  inputSchema: zodToJsonSchema2(tool.schema)
@@ -19103,7 +20912,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
19103
20912
  });
19104
20913
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
19105
20914
  const { name, arguments: args } = request.params;
19106
- const tool = lspTools.find((t) => t.name === name);
20915
+ const tool = allTools.find((t) => t.name === name);
19107
20916
  if (!tool) {
19108
20917
  return {
19109
20918
  content: [{ type: "text", text: `Unknown tool: ${name}` }],