@cloudflare/sandbox 0.0.0-0608f1e → 0.0.0-12bbd12

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.
@@ -0,0 +1,122 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "workspaces": {
4
+ "": {
5
+ "name": "sandbox-server",
6
+ "dependencies": {
7
+ "@jupyterlab/services": "^7.0.0",
8
+ "uuid": "^9.0.1",
9
+ "ws": "^8.16.0",
10
+ },
11
+ "devDependencies": {
12
+ "@types/uuid": "^9.0.7",
13
+ "@types/ws": "^8.5.10",
14
+ },
15
+ },
16
+ },
17
+ "packages": {
18
+ "@jupyter/ydoc": ["@jupyter/ydoc@3.1.0", "", { "dependencies": { "@jupyterlab/nbformat": "^3.0.0 || ^4.0.0-alpha.21 || ^4.0.0", "@lumino/coreutils": "^1.11.0 || ^2.0.0", "@lumino/disposable": "^1.10.0 || ^2.0.0", "@lumino/signaling": "^1.10.0 || ^2.0.0", "y-protocols": "^1.0.5", "yjs": "^13.5.40" } }, "sha512-+hLNUBZr8Zz8NiuaoYKINDURDJHbjFGxg8EcSU52y+rBe412TsoFxfPSng4eP7w3cZFrVlm7D+8K0nAMHxj0ZQ=="],
19
+
20
+ "@jupyterlab/coreutils": ["@jupyterlab/coreutils@6.4.5", "", { "dependencies": { "@lumino/coreutils": "^2.2.1", "@lumino/disposable": "^2.1.4", "@lumino/signaling": "^2.1.4", "minimist": "~1.2.0", "path-browserify": "^1.0.0", "url-parse": "~1.5.4" } }, "sha512-fNEefnqTNP/3alrmGYDb0Mu1heS5Cu39FIDiDDoZuuWv4bsdE2xf7VaStuwjljsQm7VZwi5aeholi1+ciBkZKg=="],
21
+
22
+ "@jupyterlab/nbformat": ["@jupyterlab/nbformat@4.4.5", "", { "dependencies": { "@lumino/coreutils": "^2.2.1" } }, "sha512-VOlm1klsb1LUNp47AL8iDZPP71pXu9a+WqukJFuuWVxg//+iR6wdqTYcunE9Lx/7SZDqMsXGD+doBZC36Lzenw=="],
23
+
24
+ "@jupyterlab/services": ["@jupyterlab/services@7.4.5", "", { "dependencies": { "@jupyter/ydoc": "^3.0.4", "@jupyterlab/coreutils": "^6.4.5", "@jupyterlab/nbformat": "^4.4.5", "@jupyterlab/settingregistry": "^4.4.5", "@jupyterlab/statedb": "^4.4.5", "@lumino/coreutils": "^2.2.1", "@lumino/disposable": "^2.1.4", "@lumino/polling": "^2.1.4", "@lumino/properties": "^2.0.3", "@lumino/signaling": "^2.1.4", "ws": "^8.11.0" } }, "sha512-JEr8+4VS2MqFyYHh4AnpK/g5V0qRBpmnfXmU0p9YFjcIgGR365D8JF0880IJ0e8+jaW88gSAvZFB8x3o9xRoAQ=="],
25
+
26
+ "@jupyterlab/settingregistry": ["@jupyterlab/settingregistry@4.4.5", "", { "dependencies": { "@jupyterlab/nbformat": "^4.4.5", "@jupyterlab/statedb": "^4.4.5", "@lumino/commands": "^2.3.2", "@lumino/coreutils": "^2.2.1", "@lumino/disposable": "^2.1.4", "@lumino/signaling": "^2.1.4", "@rjsf/utils": "^5.13.4", "ajv": "^8.12.0", "json5": "^2.2.3" }, "peerDependencies": { "react": ">=16" } }, "sha512-6hEBq3qI99VZwO97W0AM0mP1is57bWC8Vk2xudic1a90wA9G6ovUBzayqSMm/6QhTuIbCY+vruznwhhsLrVMeg=="],
27
+
28
+ "@jupyterlab/statedb": ["@jupyterlab/statedb@4.4.5", "", { "dependencies": { "@lumino/commands": "^2.3.2", "@lumino/coreutils": "^2.2.1", "@lumino/disposable": "^2.1.4", "@lumino/properties": "^2.0.3", "@lumino/signaling": "^2.1.4" } }, "sha512-UpqhOujKwoWoxtNBL2Qk0nrw+ORLJ3ckwiJg2eA0CI+n5kO3IBYAnPzak0tSS0mQNspayXr3KAm4GQ80op5yuA=="],
29
+
30
+ "@lumino/algorithm": ["@lumino/algorithm@2.0.3", "", {}, "sha512-DIcF7cIrGEC1Wh8DNjdwaL7IdcNs4Jj1VjO/90oHefeQPszKgc6DSfCxvbQiRanKR6tl/JL7tq4ZRPZES2oVAA=="],
31
+
32
+ "@lumino/commands": ["@lumino/commands@2.3.2", "", { "dependencies": { "@lumino/algorithm": "^2.0.3", "@lumino/coreutils": "^2.2.1", "@lumino/disposable": "^2.1.4", "@lumino/domutils": "^2.0.3", "@lumino/keyboard": "^2.0.3", "@lumino/signaling": "^2.1.4", "@lumino/virtualdom": "^2.0.3" } }, "sha512-aAFEiUpp2hrkQU82Z85w1L80g0iDzsQRncLBa+pqVR/k0k1lz6H9F6xZ1ff+lBumZKKtsxBxNomvd0hfxLLqGw=="],
33
+
34
+ "@lumino/coreutils": ["@lumino/coreutils@2.2.1", "", { "dependencies": { "@lumino/algorithm": "^2.0.3" } }, "sha512-yij4TnxDIum7xfFUsVvZB0oLv4shs2mNbn3juwtEIsruvVBPmurNzKX0Y8z2QetbP2AZ6MSFtBzEKsihf0H0VA=="],
35
+
36
+ "@lumino/disposable": ["@lumino/disposable@2.1.4", "", { "dependencies": { "@lumino/signaling": "^2.1.4" } }, "sha512-qTJiDbglPE2QnG4x4gtBcRbcfKQibxyyinNGKcNDrcK2TGTbbhK5PpMQ8d70l2V2Xw2pb/LfksBAg5pxkJ/G4A=="],
37
+
38
+ "@lumino/domutils": ["@lumino/domutils@2.0.3", "", {}, "sha512-bXAbZg3mf2ZDNdBBpCGDike3U+osRGHePTh8H2Ud2KwaN4g/5IryFJm/TiO4K5IYs91bWF2Zqhf3FsdbZKHlGw=="],
39
+
40
+ "@lumino/keyboard": ["@lumino/keyboard@2.0.3", "", {}, "sha512-bU2OxAR8a9eNBdV0YFjU6/lVVpbOw1gM7yHOuDGDdNu4J0UpKapFoR9gopNGSaHTmTwDtx9RHdFfIAgHwjZ+VQ=="],
41
+
42
+ "@lumino/polling": ["@lumino/polling@2.1.4", "", { "dependencies": { "@lumino/coreutils": "^2.2.1", "@lumino/disposable": "^2.1.4", "@lumino/signaling": "^2.1.4" } }, "sha512-gSkxlIJ/4/esY2G7bsRrY9A4KimDMHTo0shaD+MCbhd09fZMCWJoDMcA447/dykB1rM5NXgugNLjpdGGL/e8cw=="],
43
+
44
+ "@lumino/properties": ["@lumino/properties@2.0.3", "", {}, "sha512-zkXIU5uYz/ScHCHGl5Bt4gMYsfPxZEduZd80zqDslBWvTIMro3NnzLe66NMnecbdr5N3hDJagYyA8//Qy3XjiA=="],
45
+
46
+ "@lumino/signaling": ["@lumino/signaling@2.1.4", "", { "dependencies": { "@lumino/algorithm": "^2.0.3", "@lumino/coreutils": "^2.2.1" } }, "sha512-nC5Z6d9om369Jkh1Vp3b7C89hV4cjr1fQDVcxhemyKXwc9r6VW7FpKixC+jElcAknP5KLj1FAa8Np+K06mMkEA=="],
47
+
48
+ "@lumino/virtualdom": ["@lumino/virtualdom@2.0.3", "", { "dependencies": { "@lumino/algorithm": "^2.0.3" } }, "sha512-q2C8eBxPvvOOQjN3KuxZ+vJi082JH/GF7KwMdaWsy5g+7wjKdnXPuLQFTBLOrVqIzmbxBDlLeFr93CEhdQXcyQ=="],
49
+
50
+ "@rjsf/utils": ["@rjsf/utils@5.24.12", "", { "dependencies": { "json-schema-merge-allof": "^0.8.1", "jsonpointer": "^5.0.1", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "react-is": "^18.2.0" }, "peerDependencies": { "react": "^16.14.0 || >=17" } }, "sha512-fDwQB0XkjZjpdFUz5UAnuZj8nnbxDbX5tp+jTOjjJKw2TMQ9gFFYCQ12lSpdhezA2YgEGZfxyYTGW0DKDL5Drg=="],
51
+
52
+ "@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
53
+
54
+ "@types/uuid": ["@types/uuid@9.0.8", "", {}, "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA=="],
55
+
56
+ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
57
+
58
+ "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
59
+
60
+ "compute-gcd": ["compute-gcd@1.2.1", "", { "dependencies": { "validate.io-array": "^1.0.3", "validate.io-function": "^1.0.2", "validate.io-integer-array": "^1.0.0" } }, "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg=="],
61
+
62
+ "compute-lcm": ["compute-lcm@1.1.2", "", { "dependencies": { "compute-gcd": "^1.2.1", "validate.io-array": "^1.0.3", "validate.io-function": "^1.0.2", "validate.io-integer-array": "^1.0.0" } }, "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ=="],
63
+
64
+ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
65
+
66
+ "fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="],
67
+
68
+ "isomorphic.js": ["isomorphic.js@0.2.5", "", {}, "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw=="],
69
+
70
+ "json-schema-compare": ["json-schema-compare@0.2.2", "", { "dependencies": { "lodash": "^4.17.4" } }, "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ=="],
71
+
72
+ "json-schema-merge-allof": ["json-schema-merge-allof@0.8.1", "", { "dependencies": { "compute-lcm": "^1.1.2", "json-schema-compare": "^0.2.2", "lodash": "^4.17.20" } }, "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w=="],
73
+
74
+ "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
75
+
76
+ "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
77
+
78
+ "jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="],
79
+
80
+ "lib0": ["lib0@0.2.114", "", { "dependencies": { "isomorphic.js": "^0.2.4" }, "bin": { "0gentesthtml": "bin/gentesthtml.js", "0serve": "bin/0serve.js", "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js" } }, "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ=="],
81
+
82
+ "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
83
+
84
+ "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
85
+
86
+ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
87
+
88
+ "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
89
+
90
+ "querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="],
91
+
92
+ "react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="],
93
+
94
+ "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
95
+
96
+ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
97
+
98
+ "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="],
99
+
100
+ "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
101
+
102
+ "url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="],
103
+
104
+ "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
105
+
106
+ "validate.io-array": ["validate.io-array@1.0.6", "", {}, "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg=="],
107
+
108
+ "validate.io-function": ["validate.io-function@1.0.2", "", {}, "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ=="],
109
+
110
+ "validate.io-integer": ["validate.io-integer@1.0.5", "", { "dependencies": { "validate.io-number": "^1.0.3" } }, "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ=="],
111
+
112
+ "validate.io-integer-array": ["validate.io-integer-array@1.0.0", "", { "dependencies": { "validate.io-array": "^1.0.3", "validate.io-integer": "^1.0.4" } }, "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA=="],
113
+
114
+ "validate.io-number": ["validate.io-number@1.0.3", "", {}, "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg=="],
115
+
116
+ "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
117
+
118
+ "y-protocols": ["y-protocols@1.0.6", "", { "dependencies": { "lib0": "^0.2.85" }, "peerDependencies": { "yjs": "^13.0.0" } }, "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q=="],
119
+
120
+ "yjs": ["yjs@13.6.27", "", { "dependencies": { "lib0": "^0.2.99" } }, "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw=="],
121
+ }
122
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Circuit Breaker implementation to prevent cascading failures
3
+ */
4
+ export class CircuitBreaker {
5
+ private failures = 0;
6
+ private lastFailure: number = 0;
7
+ private successCount = 0;
8
+ private state: "closed" | "open" | "half-open" = "closed";
9
+
10
+ // Configuration
11
+ private readonly threshold: number;
12
+ private readonly timeout: number;
13
+ private readonly halfOpenSuccessThreshold: number;
14
+ private readonly name: string;
15
+
16
+ constructor(options: {
17
+ name: string;
18
+ threshold?: number;
19
+ timeout?: number;
20
+ halfOpenSuccessThreshold?: number;
21
+ }) {
22
+ this.name = options.name;
23
+ this.threshold = options.threshold || 5;
24
+ this.timeout = options.timeout || 30000; // 30 seconds
25
+ this.halfOpenSuccessThreshold = options.halfOpenSuccessThreshold || 3;
26
+ }
27
+
28
+ /**
29
+ * Execute an operation with circuit breaker protection
30
+ */
31
+ async execute<T>(operation: () => Promise<T>): Promise<T> {
32
+ // Check circuit state
33
+ if (this.state === "open") {
34
+ if (Date.now() - this.lastFailure > this.timeout) {
35
+ console.log(
36
+ `[CircuitBreaker ${this.name}] Transitioning from open to half-open`
37
+ );
38
+ this.state = "half-open";
39
+ this.successCount = 0;
40
+ } else {
41
+ throw new Error(
42
+ `Circuit breaker is open for ${this.name}. Retry after ${
43
+ this.timeout - (Date.now() - this.lastFailure)
44
+ }ms`
45
+ );
46
+ }
47
+ }
48
+
49
+ try {
50
+ const result = await operation();
51
+
52
+ // Record success
53
+ if (this.state === "half-open") {
54
+ this.successCount++;
55
+ if (this.successCount >= this.halfOpenSuccessThreshold) {
56
+ console.log(
57
+ `[CircuitBreaker ${this.name}] Transitioning from half-open to closed`
58
+ );
59
+ this.state = "closed";
60
+ this.failures = 0;
61
+ }
62
+ } else if (this.state === "closed") {
63
+ // Reset failure count on success
64
+ this.failures = 0;
65
+ }
66
+
67
+ return result;
68
+ } catch (error) {
69
+ this.recordFailure();
70
+ throw error;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Record a failure and update circuit state
76
+ */
77
+ private recordFailure() {
78
+ this.failures++;
79
+ this.lastFailure = Date.now();
80
+
81
+ if (this.state === "half-open") {
82
+ console.log(
83
+ `[CircuitBreaker ${this.name}] Failure in half-open state, transitioning to open`
84
+ );
85
+ this.state = "open";
86
+ } else if (this.failures >= this.threshold) {
87
+ console.log(
88
+ `[CircuitBreaker ${this.name}] Threshold reached (${this.failures}/${this.threshold}), transitioning to open`
89
+ );
90
+ this.state = "open";
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Get current circuit breaker state
96
+ */
97
+ getState(): {
98
+ state: string;
99
+ failures: number;
100
+ lastFailure: number;
101
+ isOpen: boolean;
102
+ } {
103
+ return {
104
+ state: this.state,
105
+ failures: this.failures,
106
+ lastFailure: this.lastFailure,
107
+ isOpen: this.state === "open",
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Reset the circuit breaker
113
+ */
114
+ reset() {
115
+ this.state = "closed";
116
+ this.failures = 0;
117
+ this.successCount = 0;
118
+ this.lastFailure = 0;
119
+ console.log(`[CircuitBreaker ${this.name}] Reset to closed state`);
120
+ }
121
+ }
@@ -1,11 +1,10 @@
1
1
  import { type SpawnOptions, spawn } from "node:child_process";
2
- import type { ExecuteRequest, SessionData } from "../types";
2
+ import type { ExecuteOptions, ExecuteRequest, SessionData } from "../types";
3
3
 
4
4
  function executeCommand(
5
5
  sessions: Map<string, SessionData>,
6
6
  command: string,
7
- sessionId?: string,
8
- background?: boolean
7
+ options: ExecuteOptions,
9
8
  ): Promise<{
10
9
  success: boolean;
11
10
  stdout: string;
@@ -16,14 +15,16 @@ function executeCommand(
16
15
  const spawnOptions: SpawnOptions = {
17
16
  shell: true,
18
17
  stdio: ["pipe", "pipe", "pipe"] as const,
19
- detached: background || false,
18
+ detached: options.background || false,
19
+ cwd: options.cwd || "/workspace", // Default to clean /workspace directory
20
+ env: options.env ? { ...process.env, ...options.env } : process.env
20
21
  };
21
22
 
22
23
  const child = spawn(command, spawnOptions);
23
24
 
24
25
  // Store the process reference for cleanup if sessionId is provided
25
- if (sessionId && sessions.has(sessionId)) {
26
- const session = sessions.get(sessionId)!;
26
+ if (options.sessionId && sessions.has(options.sessionId)) {
27
+ const session = sessions.get(options.sessionId)!;
27
28
  session.activeProcess = child;
28
29
  }
29
30
 
@@ -38,7 +39,7 @@ function executeCommand(
38
39
  stderr += data.toString();
39
40
  });
40
41
 
41
- if (background) {
42
+ if (options.background) {
42
43
  // For background processes, unref and return quickly
43
44
  child.unref();
44
45
 
@@ -61,8 +62,8 @@ function executeCommand(
61
62
  // Normal synchronous execution
62
63
  child.on("close", (code) => {
63
64
  // Clear the active process reference
64
- if (sessionId && sessions.has(sessionId)) {
65
- const session = sessions.get(sessionId)!;
65
+ if (options.sessionId && sessions.has(options.sessionId)) {
66
+ const session = sessions.get(options.sessionId)!;
66
67
  session.activeProcess = null;
67
68
  }
68
69
 
@@ -78,8 +79,8 @@ function executeCommand(
78
79
 
79
80
  child.on("error", (error) => {
80
81
  // Clear the active process reference
81
- if (sessionId && sessions.has(sessionId)) {
82
- const session = sessions.get(sessionId)!;
82
+ if (options.sessionId && sessions.has(options.sessionId)) {
83
+ const session = sessions.get(options.sessionId)!;
83
84
  session.activeProcess = null;
84
85
  }
85
86
 
@@ -96,7 +97,7 @@ export async function handleExecuteRequest(
96
97
  ): Promise<Response> {
97
98
  try {
98
99
  const body = (await req.json()) as ExecuteRequest;
99
- const { command, sessionId, background } = body;
100
+ const { command, sessionId, background, cwd, env } = body;
100
101
 
101
102
  if (!command || typeof command !== "string") {
102
103
  return new Response(
@@ -115,7 +116,7 @@ export async function handleExecuteRequest(
115
116
 
116
117
  console.log(`[Server] Executing command: ${command}`);
117
118
 
118
- const result = await executeCommand(sessions, command, sessionId, background);
119
+ const result = await executeCommand(sessions, command, { sessionId, background, cwd, env });
119
120
 
120
121
  return new Response(
121
122
  JSON.stringify({
@@ -158,7 +159,7 @@ export async function handleStreamingExecuteRequest(
158
159
  ): Promise<Response> {
159
160
  try {
160
161
  const body = (await req.json()) as ExecuteRequest;
161
- const { command, sessionId, background } = body;
162
+ const { command, sessionId, background, cwd, env } = body;
162
163
 
163
164
  if (!command || typeof command !== "string") {
164
165
  return new Response(
@@ -185,6 +186,8 @@ export async function handleStreamingExecuteRequest(
185
186
  shell: true,
186
187
  stdio: ["pipe", "pipe", "pipe"] as const,
187
188
  detached: background || false,
189
+ cwd: cwd || "/workspace", // Default to clean /workspace directory
190
+ env: env ? { ...process.env, ...env } : process.env
188
191
  };
189
192
 
190
193
  const child = spawn(command, spawnOptions);
@@ -1,8 +1,9 @@
1
1
  import { spawn } from "node:child_process";
2
- import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
3
- import { dirname } from "node:path";
2
+ import { mkdir, readdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
4
  import type {
5
5
  DeleteFileRequest,
6
+ ListFilesRequest,
6
7
  MkdirRequest,
7
8
  MoveFileRequest,
8
9
  ReadFileRequest,
@@ -842,3 +843,222 @@ export async function handleMoveFileRequest(
842
843
  }
843
844
  }
844
845
 
846
+
847
+ async function executeListFiles(
848
+ path: string,
849
+ options?: {
850
+ recursive?: boolean;
851
+ includeHidden?: boolean;
852
+ }
853
+ ): Promise<{
854
+ success: boolean;
855
+ exitCode: number;
856
+ files: Array<{
857
+ name: string;
858
+ absolutePath: string;
859
+ relativePath: string;
860
+ type: 'file' | 'directory' | 'symlink' | 'other';
861
+ size: number;
862
+ modifiedAt: string;
863
+ mode: string;
864
+ permissions: {
865
+ readable: boolean;
866
+ writable: boolean;
867
+ executable: boolean;
868
+ };
869
+ }>;
870
+ }> {
871
+ try {
872
+ const basePath = path.endsWith('/') ? path.slice(0, -1) : path;
873
+ const files: Array<{
874
+ name: string;
875
+ absolutePath: string;
876
+ relativePath: string;
877
+ type: 'file' | 'directory' | 'symlink' | 'other';
878
+ size: number;
879
+ modifiedAt: string;
880
+ mode: string;
881
+ permissions: {
882
+ readable: boolean;
883
+ writable: boolean;
884
+ executable: boolean;
885
+ };
886
+ }> = [];
887
+
888
+ // Helper function to convert numeric mode to string like "rwxr-xr-x"
889
+ function modeToString(mode: number): string {
890
+ const perms = ['---', '--x', '-w-', '-wx', 'r--', 'r-x', 'rw-', 'rwx'];
891
+ const user = (mode >> 6) & 7;
892
+ const group = (mode >> 3) & 7;
893
+ const other = mode & 7;
894
+ return perms[user] + perms[group] + perms[other];
895
+ }
896
+
897
+ // Helper function to extract permission booleans for current user
898
+ function getPermissions(mode: number): { readable: boolean; writable: boolean; executable: boolean } {
899
+ // Extract user permissions (owner permissions)
900
+ const userPerms = (mode >> 6) & 7;
901
+ return {
902
+ readable: (userPerms & 4) !== 0,
903
+ writable: (userPerms & 2) !== 0,
904
+ executable: (userPerms & 1) !== 0
905
+ };
906
+ }
907
+
908
+ async function scanDirectory(dirPath: string): Promise<void> {
909
+ const entries = await readdir(dirPath);
910
+
911
+ for (const entry of entries) {
912
+ // Skip hidden files unless includeHidden is true
913
+ if (!options?.includeHidden && entry.startsWith('.')) {
914
+ continue;
915
+ }
916
+
917
+ const fullPath = join(dirPath, entry);
918
+ const stats = await stat(fullPath);
919
+
920
+ let type: 'file' | 'directory' | 'symlink' | 'other';
921
+ if (stats.isDirectory()) {
922
+ type = 'directory';
923
+ } else if (stats.isFile()) {
924
+ type = 'file';
925
+ } else if (stats.isSymbolicLink()) {
926
+ type = 'symlink';
927
+ } else {
928
+ type = 'other';
929
+ }
930
+
931
+ // Extract mode (permissions) - stats.mode is a number
932
+ const mode = stats.mode & 0o777; // Get only permission bits
933
+
934
+ // Calculate relative path from base directory
935
+ const relativePath = fullPath.startsWith(`${basePath}/`)
936
+ ? fullPath.substring(basePath.length + 1)
937
+ : fullPath === basePath
938
+ ? '.'
939
+ : entry;
940
+
941
+ const fileInfo = {
942
+ name: entry,
943
+ absolutePath: fullPath,
944
+ relativePath,
945
+ type,
946
+ size: stats.size,
947
+ modifiedAt: stats.mtime.toISOString(),
948
+ mode: modeToString(mode),
949
+ permissions: getPermissions(mode)
950
+ };
951
+
952
+ files.push(fileInfo);
953
+
954
+ // Recursively scan subdirectories if requested
955
+ if (options?.recursive && type === 'directory') {
956
+ await scanDirectory(fullPath);
957
+ }
958
+ }
959
+ }
960
+
961
+ await scanDirectory(path);
962
+
963
+ console.log(`[Server] Listed ${files.length} files in: ${path}`);
964
+ return {
965
+ exitCode: 0,
966
+ files,
967
+ success: true,
968
+ };
969
+ } catch (error) {
970
+ console.error(`[Server] Error listing files in: ${path}`, error);
971
+ throw error;
972
+ }
973
+ }
974
+
975
+ export async function handleListFilesRequest(
976
+ req: Request,
977
+ corsHeaders: Record<string, string>
978
+ ): Promise<Response> {
979
+ try {
980
+ const body = (await req.json()) as ListFilesRequest;
981
+ const { path, options } = body;
982
+
983
+ if (!path || typeof path !== "string") {
984
+ return new Response(
985
+ JSON.stringify({
986
+ error: "Path is required and must be a string",
987
+ }),
988
+ {
989
+ headers: {
990
+ "Content-Type": "application/json",
991
+ ...corsHeaders,
992
+ },
993
+ status: 400,
994
+ }
995
+ );
996
+ }
997
+
998
+ // Basic safety check - prevent dangerous paths
999
+ const dangerousPatterns = [
1000
+ /^\/etc/, // System directories
1001
+ /^\/var/, // System directories
1002
+ /^\/usr/, // System directories
1003
+ /^\/bin/, // System directories
1004
+ /^\/sbin/, // System directories
1005
+ /^\/boot/, // System directories
1006
+ /^\/dev/, // System directories
1007
+ /^\/proc/, // System directories
1008
+ /^\/sys/, // System directories
1009
+ /^\/tmp\/\.\./, // Path traversal attempts
1010
+ /\.\./, // Path traversal attempts
1011
+ ];
1012
+
1013
+ if (dangerousPatterns.some((pattern) => pattern.test(path))) {
1014
+ return new Response(
1015
+ JSON.stringify({
1016
+ error: "Dangerous path not allowed",
1017
+ }),
1018
+ {
1019
+ headers: {
1020
+ "Content-Type": "application/json",
1021
+ ...corsHeaders,
1022
+ },
1023
+ status: 400,
1024
+ }
1025
+ );
1026
+ }
1027
+
1028
+ console.log(`[Server] Listing files in: ${path}`);
1029
+
1030
+ const result = await executeListFiles(path, options);
1031
+
1032
+ return new Response(
1033
+ JSON.stringify({
1034
+ exitCode: result.exitCode,
1035
+ files: result.files,
1036
+ path,
1037
+ success: result.success,
1038
+ timestamp: new Date().toISOString(),
1039
+ }),
1040
+ {
1041
+ headers: {
1042
+ "Content-Type": "application/json",
1043
+ ...corsHeaders,
1044
+ },
1045
+ }
1046
+ );
1047
+ } catch (error) {
1048
+ console.error("[Server] Error in handleListFilesRequest:", error);
1049
+ return new Response(
1050
+ JSON.stringify({
1051
+ error: "Failed to list files",
1052
+ message: error instanceof Error ? error.message : "Unknown error",
1053
+ }),
1054
+ {
1055
+ headers: {
1056
+ "Content-Type": "application/json",
1057
+ ...corsHeaders,
1058
+ },
1059
+ status: 500,
1060
+ }
1061
+ );
1062
+ }
1063
+ }
1064
+
@@ -72,7 +72,7 @@ export async function handleStartProcessRequest(
72
72
  // Start the actual process
73
73
  try {
74
74
  const spawnOptions: SpawnOptions = {
75
- cwd: options.cwd || process.cwd(),
75
+ cwd: options.cwd || "/workspace", // Default to /workspace for consistency with exec commands
76
76
  env: { ...process.env, ...options.env },
77
77
  detached: false,
78
78
  shell: true,