@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.
- package/CHANGELOG.md +58 -0
- package/Dockerfile +44 -18
- package/README.md +784 -0
- package/container_src/bun.lock +122 -0
- package/container_src/circuit-breaker.ts +121 -0
- package/container_src/handler/exec.ts +17 -14
- package/container_src/handler/file.ts +222 -2
- package/container_src/handler/process.ts +1 -1
- package/container_src/index.ts +312 -10
- package/container_src/jupyter-server.ts +579 -0
- package/container_src/jupyter-service.ts +461 -0
- package/container_src/jupyter_config.py +48 -0
- package/container_src/mime-processor.ts +255 -0
- package/container_src/package.json +9 -0
- package/container_src/startup.sh +84 -0
- package/container_src/types.ts +17 -3
- package/package.json +5 -4
- package/src/client.ts +108 -122
- package/src/errors.ts +218 -0
- package/src/index.ts +51 -15
- package/src/interpreter-types.ts +383 -0
- package/src/interpreter.ts +150 -0
- package/src/jupyter-client.ts +349 -0
- package/src/sandbox.ts +292 -149
- package/src/types.ts +125 -0
- package/tsconfig.json +1 -1
|
@@ -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
|
-
|
|
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 ||
|
|
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,
|