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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/analyst.md +1 -1
- package/agents/architect-low.md +1 -1
- package/agents/architect-medium.md +1 -1
- package/agents/architect.md +1 -1
- package/agents/build-fixer-low.md +0 -1
- package/agents/build-fixer.md +0 -1
- package/agents/code-reviewer-low.md +1 -1
- package/agents/code-reviewer.md +1 -1
- package/agents/critic.md +1 -1
- package/agents/designer-high.md +0 -1
- package/agents/designer-low.md +0 -1
- package/agents/designer.md +0 -1
- package/agents/executor-high.md +0 -1
- package/agents/executor-low.md +0 -1
- package/agents/executor.md +0 -1
- package/agents/explore-high.md +1 -1
- package/agents/explore-medium.md +1 -1
- package/agents/explore.md +1 -1
- package/agents/planner.md +0 -1
- package/agents/qa-tester-high.md +0 -1
- package/agents/qa-tester.md +0 -1
- package/agents/researcher-low.md +1 -1
- package/agents/researcher.md +1 -1
- package/agents/scientist-high.md +1 -1
- package/agents/scientist-low.md +1 -1
- package/agents/scientist.md +1 -1
- package/agents/security-reviewer-low.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/tdd-guide-low.md +0 -1
- package/agents/tdd-guide.md +0 -1
- package/agents/vision.md +1 -1
- package/agents/writer.md +0 -1
- package/bridge/mcp-server.cjs +1885 -76
- package/commands/omc-setup.md +39 -0
- package/dist/mcp/standalone-server.js +13 -5
- package/dist/mcp/standalone-server.js.map +1 -1
- package/dist/tools/ast-tools.d.ts.map +1 -1
- package/dist/tools/ast-tools.js +35 -1
- package/dist/tools/ast-tools.js.map +1 -1
- package/package.json +1 -1
package/bridge/mcp-server.cjs
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
496
|
-
this.code = optimizeExpr(this.code, names,
|
|
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,
|
|
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,
|
|
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,
|
|
583
|
+
optimizeNames(names, constants2) {
|
|
584
584
|
var _a;
|
|
585
|
-
this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names,
|
|
586
|
-
if (!(super.optimizeNames(names,
|
|
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,
|
|
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,
|
|
612
|
-
if (!super.optimizeNames(names,
|
|
611
|
+
optimizeNames(names, constants2) {
|
|
612
|
+
if (!super.optimizeNames(names, constants2))
|
|
613
613
|
return;
|
|
614
|
-
this.iteration = optimizeExpr(this.iteration, names,
|
|
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,
|
|
651
|
-
if (!super.optimizeNames(names,
|
|
650
|
+
optimizeNames(names, constants2) {
|
|
651
|
+
if (!super.optimizeNames(names, constants2))
|
|
652
652
|
return;
|
|
653
|
-
this.iterable = optimizeExpr(this.iterable, names,
|
|
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,
|
|
695
|
+
optimizeNames(names, constants2) {
|
|
696
696
|
var _a, _b;
|
|
697
|
-
super.optimizeNames(names,
|
|
698
|
-
(_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names,
|
|
699
|
-
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names,
|
|
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,
|
|
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 =
|
|
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 &&
|
|
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 =
|
|
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
|
|
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(
|
|
3227
|
-
let input =
|
|
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 [
|
|
3427
|
-
wsComponent.path =
|
|
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(
|
|
3579
|
+
serialize(parse5(uri, options), options);
|
|
3580
3580
|
} else if (typeof uri === "object") {
|
|
3581
3581
|
uri = /** @type {T} */
|
|
3582
|
-
|
|
3582
|
+
parse5(serialize(uri, options), options);
|
|
3583
3583
|
}
|
|
3584
3584
|
return uri;
|
|
3585
3585
|
}
|
|
3586
|
-
function
|
|
3586
|
+
function resolve4(baseURI, relativeURI, options) {
|
|
3587
3587
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3588
|
-
const resolved = resolveComponent(
|
|
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 =
|
|
3596
|
-
relative =
|
|
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(
|
|
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(
|
|
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
|
|
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:
|
|
3813
|
+
resolve: resolve4,
|
|
3814
3814
|
resolveComponent,
|
|
3815
3815
|
equal,
|
|
3816
3816
|
serialize,
|
|
3817
|
-
parse:
|
|
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,
|
|
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,
|
|
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 = [...
|
|
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,
|
|
7388
|
+
constructor(parent, value, path6, key) {
|
|
7389
7389
|
this._cachedPath = [];
|
|
7390
7390
|
this.parent = parent;
|
|
7391
7391
|
this.data = value;
|
|
7392
|
-
this._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,
|
|
11030
|
-
if (!
|
|
11029
|
+
function getElementAtPath(obj, path6) {
|
|
11030
|
+
if (!path6)
|
|
11031
11031
|
return obj;
|
|
11032
|
-
return
|
|
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(
|
|
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(
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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(
|
|
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((
|
|
17733
|
+
return new Promise((resolve4) => {
|
|
17734
17734
|
const json = serializeMessage(message);
|
|
17735
17735
|
if (this._stdout.write(json)) {
|
|
17736
|
-
|
|
17736
|
+
resolve4();
|
|
17737
17737
|
} else {
|
|
17738
|
-
this._stdout.once("drain",
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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:
|
|
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((
|
|
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
|
|
18323
|
+
const path6 = uriToPath(location.uri);
|
|
18324
18324
|
const range = formatRange(location.range);
|
|
18325
|
-
return `${
|
|
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
|
|
18412
|
-
lines.push(`File: ${
|
|
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
|
|
18423
|
-
lines.push(`File: ${
|
|
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((
|
|
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((
|
|
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:
|
|
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 =
|
|
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}` }],
|