@aoagents/ao-web 0.2.3 → 0.2.5
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/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +228 -221
- package/.next/app-path-routes-manifest.json +7 -6
- package/.next/build-manifest.json +14 -14
- package/.next/next-minimal-server.js.nft.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +31 -31
- package/.next/react-loadable-manifest.json +14 -14
- package/.next/required-server-files.json +7 -4
- package/.next/server/app/_not-found/page.js +2 -2
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +15 -15
- package/.next/server/app/api/backlog/route.js +1 -1
- package/.next/server/app/api/backlog/route.js.nft.json +1 -1
- package/.next/server/app/api/backlog/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/events/route.js +2 -2
- package/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/server/app/api/events/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/issues/route.js +1 -1
- package/.next/server/app/api/issues/route.js.nft.json +1 -1
- package/.next/server/app/api/issues/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/observability/route.js +1 -1
- package/.next/server/app/api/observability/route.js.nft.json +1 -1
- package/.next/server/app/api/observability/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/orchestrators/route.js +1 -1
- package/.next/server/app/api/orchestrators/route.js.nft.json +1 -1
- package/.next/server/app/api/orchestrators/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/route.js +1 -1
- package/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/prs/[id]/merge/route.js +1 -1
- package/.next/server/app/api/prs/[id]/merge/route.js.nft.json +1 -1
- package/.next/server/app/api/prs/[id]/merge/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/runtime/terminal/route.js +1 -1
- package/.next/server/app/api/runtime/terminal/route.js.nft.json +1 -1
- package/.next/server/app/api/runtime/terminal/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/kill/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/kill/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/kill/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/message/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/message/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/message/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/remap/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/remap/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/remap/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/restore/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/send/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/send/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/send/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/patches/route.js +1 -0
- package/.next/server/app/api/sessions/patches/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/patches/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/setup-labels/route.js +1 -1
- package/.next/server/app/api/setup-labels/route.js.nft.json +1 -1
- package/.next/server/app/api/setup-labels/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/spawn/route.js +1 -1
- package/.next/server/app/api/spawn/route.js.nft.json +1 -1
- package/.next/server/app/api/spawn/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verify/route.js +1 -1
- package/.next/server/app/api/verify/route.js.nft.json +1 -1
- package/.next/server/app/api/verify/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/webhooks/[...slug]/route.js +1 -1
- package/.next/server/app/api/webhooks/[...slug]/route.js.nft.json +1 -1
- package/.next/server/app/api/webhooks/[...slug]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/apple-icon/route.js +1 -1
- package/.next/server/app/apple-icon/route.js.nft.json +1 -1
- package/.next/server/app/apple-icon/route_client-reference-manifest.js +1 -1
- package/.next/server/app/dev/terminal-test/page.js +3 -3
- package/.next/server/app/dev/terminal-test/page.js.nft.json +1 -1
- package/.next/server/app/dev/terminal-test/page_client-reference-manifest.js +1 -1
- package/.next/server/app/dev/terminal-test.html +1 -1
- package/.next/server/app/dev/terminal-test.rsc +17 -17
- package/.next/server/app/icon/route.js +1 -1
- package/.next/server/app/icon/route.js.nft.json +1 -1
- package/.next/server/app/icon/route_client-reference-manifest.js +1 -1
- package/.next/server/app/icon-192/route.js +1 -1
- package/.next/server/app/icon-192/route.js.nft.json +1 -1
- package/.next/server/app/icon-192/route_client-reference-manifest.js +1 -1
- package/.next/server/app/icon-512/route.js +1 -1
- package/.next/server/app/icon-512/route.js.nft.json +1 -1
- package/.next/server/app/icon-512/route_client-reference-manifest.js +1 -1
- package/.next/server/app/manifest.webmanifest/route.js +2 -2
- package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -1
- package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
- package/.next/server/app/manifest.webmanifest.body +1 -1
- package/.next/server/app/orchestrators/page.js +2 -2
- package/.next/server/app/orchestrators/page.js.nft.json +1 -1
- package/.next/server/app/orchestrators/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page.js +2 -2
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/prs/page.js +2 -2
- package/.next/server/app/prs/page.js.nft.json +1 -1
- package/.next/server/app/prs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/sessions/[id]/page.js +5 -5
- package/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
- package/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/test-direct/page.js +2 -2
- package/.next/server/app/test-direct/page.js.nft.json +1 -1
- package/.next/server/app/test-direct/page_client-reference-manifest.js +1 -1
- package/.next/server/app/test-direct.html +1 -1
- package/.next/server/app/test-direct.rsc +17 -17
- package/.next/server/app-paths-manifest.json +7 -6
- package/.next/server/chunks/100.js +1 -0
- package/.next/server/chunks/106.js +1 -0
- package/.next/server/chunks/172.js +9 -0
- package/.next/server/chunks/180.js +25 -0
- package/.next/server/chunks/333.js +1 -0
- package/.next/server/chunks/367.js +3 -0
- package/.next/server/chunks/561.js +22 -0
- package/.next/server/chunks/627.js +380 -363
- package/.next/server/chunks/803.js +6 -0
- package/.next/server/chunks/886.js +384 -0
- package/.next/server/chunks/907.js +1 -0
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/next-font-manifest.js +1 -1
- package/.next/server/next-font-manifest.json +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages/_app.js +1 -1
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js +1 -1
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js +3 -3
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/OyzSw2fQpoJHXyoBWoZ2X/_buildManifest.js +1 -0
- package/.next/static/chunks/1461-af7c54935f21d56d.js +1 -0
- package/.next/static/chunks/{9393.acf1934a190d793b.js → 2529.32352c1ce5253e3e.js} +1 -1
- package/.next/static/chunks/{7411.ecda44797fb514a0.js → 5491.fd98884d48631149.js} +1 -1
- package/.next/static/chunks/6923-ef8434dfec6f2a3c.js +1 -0
- package/.next/static/chunks/7097.fc904e5f313c4994.js +1 -0
- package/.next/static/chunks/7317.685aa5231218e8d3.js +1 -0
- package/.next/static/chunks/8713-d3d663f55dc00e48.js +1 -0
- package/.next/static/chunks/8785-6b4b3ff260617997.js +1 -0
- package/.next/static/chunks/88a6fc35-f836b4b72df5eafa.js +1 -0
- package/.next/static/chunks/9478.656c9c263bc55893.js +1 -0
- package/.next/static/chunks/app/_not-found/page-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/backlog/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/events/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/issues/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/observability/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/orchestrators/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/projects/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/prs/[id]/merge/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/runtime/terminal/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/kill/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/message/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/remap/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/restore/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/send/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/sessions/patches/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/setup-labels/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/spawn/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/verify/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/api/webhooks/[...slug]/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/apple-icon/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/dev/terminal-test/page-4912fa9e6459f124.js +1 -0
- package/.next/static/chunks/app/error-670f1d8bf2b6859c.js +1 -0
- package/.next/static/chunks/app/{global-error-7b5c8ae45329c659.js → global-error-ca06d2b1be2d4ae0.js} +1 -1
- package/.next/static/chunks/app/icon/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/icon-192/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/icon-512/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/layout-ddf79478d9a673bb.js +1 -0
- package/.next/static/chunks/app/loading-019391920005654f.js +1 -0
- package/.next/static/chunks/app/manifest.webmanifest/route-019391920005654f.js +1 -0
- package/.next/static/chunks/app/{not-found-3772c2e09c29d80f.js → not-found-824d5d3c6e296eeb.js} +1 -1
- package/.next/static/chunks/app/orchestrators/page-4da04720a09483d2.js +1 -0
- package/.next/static/chunks/app/page-abf3e3c98cac072f.js +1 -0
- package/.next/static/chunks/app/prs/page-46455c406183ffbf.js +1 -0
- package/.next/static/chunks/app/sessions/[id]/error-eb0973907da68a37.js +1 -0
- package/.next/static/chunks/app/sessions/[id]/loading-019391920005654f.js +1 -0
- package/.next/static/chunks/app/sessions/[id]/{not-found-3772c2e09c29d80f.js → not-found-824d5d3c6e296eeb.js} +1 -1
- package/.next/static/chunks/app/sessions/[id]/page-7eb67fd32f39b15f.js +1 -0
- package/.next/static/chunks/app/test-direct/page-1fd63edb9a9ca8be.js +1 -0
- package/.next/static/chunks/{df4ed4d4.6a752eba3933e9a8.js → df4ed4d4.b6997d8b8ce9d79b.js} +1 -1
- package/.next/static/chunks/framework-7060e2ac4971c604.js +1 -0
- package/.next/static/chunks/main-app-690acf9d5d2050c9.js +1 -0
- package/.next/static/chunks/main-ed1610689fbd6f0d.js +1 -0
- package/.next/static/chunks/pages/_app-f4baf4dbe88f6f54.js +1 -0
- package/.next/static/chunks/pages/_error-a7f6723f42093f29.js +1 -0
- package/.next/static/chunks/{webpack-e12ceebeb7a1cc7e.js → webpack-ab6c08c78ffc8113.js} +1 -1
- package/.next/static/css/a5398483e6b75ad3.css +1 -0
- package/LICENSE +21 -0
- package/dist-server/direct-terminal-ws.js +33 -236
- package/dist-server/mux-websocket.js +516 -0
- package/dist-server/start-all.js +0 -2
- package/next.config.js +1 -0
- package/package.json +27 -27
- package/.next/server/chunks/27.js +0 -438
- package/.next/server/chunks/377.js +0 -1
- package/.next/server/chunks/393.js +0 -1
- package/.next/server/chunks/639.js +0 -6
- package/.next/server/chunks/693.js +0 -1
- package/.next/server/chunks/705.js +0 -22
- package/.next/server/chunks/787.js +0 -29
- package/.next/server/chunks/796.js +0 -1
- package/.next/server/chunks/934.js +0 -1
- package/.next/server/chunks/956.js +0 -9
- package/.next/static/chunks/1250-e7cf6b069fbb03ed.js +0 -1
- package/.next/static/chunks/2205.498806f73783aa54.js +0 -1
- package/.next/static/chunks/3698-9c12c45b8184022c.js +0 -1
- package/.next/static/chunks/6381.1541d5695a727108.js +0 -1
- package/.next/static/chunks/7505-2d2422d31862995f.js +0 -1
- package/.next/static/chunks/8597-1385f90ec1cebf47.js +0 -1
- package/.next/static/chunks/8762.f3d526855363db16.js +0 -1
- package/.next/static/chunks/a51c26f2-a21e680a5df6764e.js +0 -1
- package/.next/static/chunks/app/_not-found/page-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/backlog/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/events/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/issues/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/observability/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/orchestrators/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/projects/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/prs/[id]/merge/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/runtime/terminal/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/kill/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/message/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/remap/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/restore/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/send/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/setup-labels/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/spawn/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/verify/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/api/webhooks/[...slug]/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/apple-icon/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/dev/terminal-test/page-ac0ce5b046fcad82.js +0 -1
- package/.next/static/chunks/app/error-4896c9d3b7681a80.js +0 -1
- package/.next/static/chunks/app/icon/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/icon-192/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/icon-512/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/layout-023f2083204e4f68.js +0 -1
- package/.next/static/chunks/app/loading-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/manifest.webmanifest/route-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/orchestrators/page-df85236df674fc5a.js +0 -1
- package/.next/static/chunks/app/page-e97da633b7ef25f2.js +0 -1
- package/.next/static/chunks/app/prs/page-8cc0fce584d238c5.js +0 -1
- package/.next/static/chunks/app/sessions/[id]/error-90bc99b777fecabf.js +0 -1
- package/.next/static/chunks/app/sessions/[id]/loading-2224bc1d3dce1b3e.js +0 -1
- package/.next/static/chunks/app/sessions/[id]/page-c9c95a8604786cff.js +0 -1
- package/.next/static/chunks/app/test-direct/page-16ceeb9f7664394a.js +0 -1
- package/.next/static/chunks/framework-f8b8ba0f71d38056.js +0 -1
- package/.next/static/chunks/main-2bc85c765bf1fc49.js +0 -1
- package/.next/static/chunks/main-app-93fb36c3bd1a6739.js +0 -1
- package/.next/static/chunks/pages/_app-a0b975794f15bd68.js +0 -1
- package/.next/static/chunks/pages/_error-5f4e3b5eea57917d.js +0 -1
- package/.next/static/css/908f93fdd7ffba42.css +0 -1
- package/.next/static/oB_TdR1wCu7ot1D0AG_cw/_buildManifest.js +0 -1
- package/dist-server/terminal-websocket.js +0 -375
- /package/.next/static/{oB_TdR1wCu7ot1D0AG_cw → OyzSw2fQpoJHXyoBWoZ2X}/_ssgManifest.js +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
self.__BUILD_MANIFEST=function(e,r,t,_){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:22,errorRate:1e-4,numBits:422,numHashes:14,bitArray:[1,0,1,e,e,0,r,r,e,r,e,e,r,r,r,r,r,e,r,e,r,e,e,e,e,e,r,e,r,r,e,r,r,r,e,e,r,r,r,e,e,r,r,r,r,r,r,e,e,e,r,r,r,e,r,r,r,e,e,e,e,e,r,e,r,e,e,e,e,e,e,r,r,e,e,e,r,e,r,e,e,r,r,e,r,e,r,r,r,r,r,e,e,r,e,e,r,e,r,e,r,e,r,r,e,r,e,e,r,e,e,r,r,r,r,e,e,r,e,e,e,e,r,e,e,e,e,e,e,e,e,e,e,e,e,r,r,r,r,r,r,e,e,e,r,r,r,r,r,r,e,e,e,r,r,r,e,e,e,e,e,r,e,r,e,e,e,r,e,e,r,e,e,e,r,r,r,e,r,e,r,e,e,r,e,e,e,r,r,r,e,e,r,e,e,r,r,r,r,e,r,r,e,r,e,e,e,r,e,e,e,r,e,e,e,r,r,e,e,e,r,e,r,e,r,r,r,r,r,e,r,r,e,r,e,r,r,e,e,r,r,r,r,r,e,e,r,e,e,r,e,e,e,r,r,r,e,r,e,r,e,e,r,r,r,r,e,e,e,e,r,r,r,e,e,e,r,r,e,e,r,r,r,r,e,r,r,e,e,r,r,e,e,r,r,r,e,r,e,r,r,e,r,r,r,r,e,e,e,r,r,e,r,r,e,r,e,e,r,r,e,r,r,r,e,e,r,r,r,e,e,e,e,e,r,r,r,e,e,e,r,e,r,r,r,r,r,r,e,e,e,r,e,r,r,r,r,r,r,r,e,e,e,r,e,e,e,e,e,e,r,e,e,e,e,e,r,e,e,e,r,e,e,r,r,r,e,e,e,r,r,e,r,e,r,r,e,r,r,e,r,e,e,r,e,r,e,e,e,e,e,r,e,e,r,e,r,e,r,e,e,e]},__routerFilterDynamic:{numItems:4,errorRate:1e-4,numBits:77,numHashes:14,bitArray:[r,r,e,r,r,e,r,e,e,r,e,r,e,e,r,e,e,e,r,e,r,e,e,e,r,e,r,r,e,r,e,e,e,e,e,e,e,r,r,r,e,e,e,e,e,e,e,e,e,e,e,r,r,r,r,r,e,e,e,e,r,r,r,r,e,e,r,e,e,r,e,e,r,r,r,r,e]},"/_error":["static/chunks/pages/_error-5f4e3b5eea57917d.js"],sortedPages:["/_app","/_error"]}}(1,0,1e-4,14),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Terminal server that manages ttyd instances for tmux sessions.
|
|
3
|
-
*
|
|
4
|
-
* Runs alongside Next.js. Spawns a ttyd process per session on demand,
|
|
5
|
-
* each on a unique port. The dashboard embeds ttyd via iframe.
|
|
6
|
-
*
|
|
7
|
-
* ttyd handles all the hard parts: xterm.js, WebSocket, ANSI rendering,
|
|
8
|
-
* cursor positioning, resize, input — battle-tested and correct.
|
|
9
|
-
*
|
|
10
|
-
* TODO: Add authentication middleware to verify:
|
|
11
|
-
* - User is authenticated
|
|
12
|
-
* - User owns the requested session
|
|
13
|
-
* - Rate limiting for terminal access
|
|
14
|
-
*/
|
|
15
|
-
import { spawn } from "node:child_process";
|
|
16
|
-
import { createServer, request } from "node:http";
|
|
17
|
-
import { createCorrelationId } from "@aoagents/ao-core";
|
|
18
|
-
import { findTmux, resolveTmuxSession, validateSessionId } from "./tmux-utils.js";
|
|
19
|
-
import { createObserverContext, inferProjectId } from "./terminal-observability.js";
|
|
20
|
-
/** Cached full path to tmux binary */
|
|
21
|
-
const TMUX = findTmux();
|
|
22
|
-
console.log(`[Terminal] Using tmux: ${TMUX}`);
|
|
23
|
-
const instances = new Map();
|
|
24
|
-
const metrics = {
|
|
25
|
-
activeInstances: 0,
|
|
26
|
-
totalSpawns: 0,
|
|
27
|
-
totalErrors: 0,
|
|
28
|
-
totalReused: 0,
|
|
29
|
-
};
|
|
30
|
-
const availablePorts = new Set(); // Pool of recycled ports
|
|
31
|
-
let nextPort = 7800; // Start ttyd instances from port 7800
|
|
32
|
-
const MAX_PORT = 7900; // Prevent unbounded port allocation
|
|
33
|
-
const { config: observabilityConfig, observer } = createObserverContext("terminal-websocket");
|
|
34
|
-
function recordWebsocketMetric(input) {
|
|
35
|
-
if (!observer) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const correlationId = createCorrelationId("ws");
|
|
39
|
-
observer.recordOperation({
|
|
40
|
-
metric: input.metric,
|
|
41
|
-
operation: `terminal.websocket.${input.metric}`,
|
|
42
|
-
outcome: input.outcome,
|
|
43
|
-
correlationId,
|
|
44
|
-
projectId: input.sessionId ? inferProjectId(observabilityConfig, input.sessionId) : undefined,
|
|
45
|
-
sessionId: input.sessionId,
|
|
46
|
-
reason: input.reason,
|
|
47
|
-
data: input.data,
|
|
48
|
-
level: input.outcome === "failure" ? "error" : "info",
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Check if ttyd is ready to accept connections by making a test request.
|
|
53
|
-
* Returns a promise that resolves when ttyd is ready or rejects after timeout.
|
|
54
|
-
* Properly cancels pending timeouts and requests to prevent memory leaks.
|
|
55
|
-
*/
|
|
56
|
-
function waitForTtyd(port, sessionId, timeoutMs = 3000) {
|
|
57
|
-
const startTime = Date.now();
|
|
58
|
-
let timeoutId = null;
|
|
59
|
-
let pendingReq = null;
|
|
60
|
-
let settled = false;
|
|
61
|
-
return new Promise((resolve, reject) => {
|
|
62
|
-
const cleanup = () => {
|
|
63
|
-
settled = true;
|
|
64
|
-
if (timeoutId) {
|
|
65
|
-
clearTimeout(timeoutId);
|
|
66
|
-
timeoutId = null;
|
|
67
|
-
}
|
|
68
|
-
if (pendingReq) {
|
|
69
|
-
pendingReq.destroy();
|
|
70
|
-
pendingReq = null;
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
const checkReady = () => {
|
|
74
|
-
if (settled)
|
|
75
|
-
return;
|
|
76
|
-
if (Date.now() - startTime > timeoutMs) {
|
|
77
|
-
cleanup();
|
|
78
|
-
reject(new Error(`ttyd did not become ready within ${timeoutMs}ms`));
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const req = request({
|
|
82
|
-
hostname: "localhost",
|
|
83
|
-
port,
|
|
84
|
-
path: `/${sessionId}/`,
|
|
85
|
-
method: "GET",
|
|
86
|
-
timeout: 500,
|
|
87
|
-
}, (_res) => {
|
|
88
|
-
// Any response (even 404) means ttyd is listening
|
|
89
|
-
cleanup();
|
|
90
|
-
resolve();
|
|
91
|
-
});
|
|
92
|
-
pendingReq = req;
|
|
93
|
-
req.on("timeout", () => {
|
|
94
|
-
if (settled)
|
|
95
|
-
return;
|
|
96
|
-
req.destroy();
|
|
97
|
-
pendingReq = null;
|
|
98
|
-
// Schedule retry but track the timeout ID
|
|
99
|
-
timeoutId = setTimeout(checkReady, 100);
|
|
100
|
-
});
|
|
101
|
-
req.on("error", () => {
|
|
102
|
-
if (settled)
|
|
103
|
-
return;
|
|
104
|
-
pendingReq = null;
|
|
105
|
-
// Connection refused or other error - ttyd not ready yet, retry
|
|
106
|
-
timeoutId = setTimeout(checkReady, 100);
|
|
107
|
-
});
|
|
108
|
-
req.end();
|
|
109
|
-
};
|
|
110
|
-
checkReady();
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Spawn or reuse a ttyd instance for a tmux session.
|
|
115
|
-
*
|
|
116
|
-
* @param sessionId - User-facing session ID (used for base-path and URL)
|
|
117
|
-
* @param tmuxSessionName - Actual tmux session name (may be hash-prefixed)
|
|
118
|
-
*/
|
|
119
|
-
function getOrSpawnTtyd(sessionId, tmuxSessionName) {
|
|
120
|
-
const existing = instances.get(sessionId);
|
|
121
|
-
if (existing) {
|
|
122
|
-
metrics.totalReused += 1;
|
|
123
|
-
recordWebsocketMetric({
|
|
124
|
-
metric: "websocket_connect",
|
|
125
|
-
outcome: "success",
|
|
126
|
-
sessionId,
|
|
127
|
-
data: { reused: true, port: existing.port },
|
|
128
|
-
});
|
|
129
|
-
return existing;
|
|
130
|
-
}
|
|
131
|
-
// Allocate port: reuse from pool if available, otherwise increment
|
|
132
|
-
let port;
|
|
133
|
-
if (availablePorts.size > 0) {
|
|
134
|
-
// Reuse a recycled port
|
|
135
|
-
port = availablePorts.values().next().value;
|
|
136
|
-
availablePorts.delete(port);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
// Allocate new port
|
|
140
|
-
if (nextPort >= MAX_PORT) {
|
|
141
|
-
throw new Error(`Port exhaustion: reached maximum of ${MAX_PORT - 7800} terminal instances`);
|
|
142
|
-
}
|
|
143
|
-
port = nextPort++;
|
|
144
|
-
}
|
|
145
|
-
console.log(`[Terminal] Spawning ttyd for ${tmuxSessionName} on port ${port}`);
|
|
146
|
-
metrics.totalSpawns += 1;
|
|
147
|
-
metrics.lastSpawnAt = new Date().toISOString();
|
|
148
|
-
// Enable mouse mode for scrollback support
|
|
149
|
-
const mouseProc = spawn(TMUX, ["set-option", "-t", tmuxSessionName, "mouse", "on"]);
|
|
150
|
-
mouseProc.on("error", (err) => {
|
|
151
|
-
console.error(`[Terminal] Failed to set mouse mode for ${tmuxSessionName}:`, err.message);
|
|
152
|
-
});
|
|
153
|
-
// Hide the green status bar for cleaner appearance
|
|
154
|
-
const statusProc = spawn(TMUX, ["set-option", "-t", tmuxSessionName, "status", "off"]);
|
|
155
|
-
statusProc.on("error", (err) => {
|
|
156
|
-
console.error(`[Terminal] Failed to hide status bar for ${tmuxSessionName}:`, err.message);
|
|
157
|
-
});
|
|
158
|
-
// Use user-facing sessionId for base-path (matches URL the dashboard uses)
|
|
159
|
-
// Use tmuxSessionName for tmux attach (may be hash-prefixed)
|
|
160
|
-
const proc = spawn("ttyd", [
|
|
161
|
-
"--writable",
|
|
162
|
-
"--port",
|
|
163
|
-
String(port),
|
|
164
|
-
"--base-path",
|
|
165
|
-
`/${sessionId}`,
|
|
166
|
-
TMUX,
|
|
167
|
-
"attach-session",
|
|
168
|
-
"-t",
|
|
169
|
-
tmuxSessionName,
|
|
170
|
-
], {
|
|
171
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
172
|
-
});
|
|
173
|
-
proc.stdout?.on("data", (data) => {
|
|
174
|
-
console.log(`[Terminal] ttyd ${sessionId}: ${data.toString().trim()}`);
|
|
175
|
-
});
|
|
176
|
-
proc.stderr?.on("data", (data) => {
|
|
177
|
-
console.log(`[Terminal] ttyd ${sessionId}: ${data.toString().trim()}`);
|
|
178
|
-
});
|
|
179
|
-
// Use once() for cleanup handlers to prevent race condition when both exit and error fire
|
|
180
|
-
proc.once("exit", (code) => {
|
|
181
|
-
console.log(`[Terminal] ttyd ${sessionId} exited with code ${code}`);
|
|
182
|
-
// Only delete if this is still the current instance (prevents race with error handler)
|
|
183
|
-
const current = instances.get(sessionId);
|
|
184
|
-
if (current?.process === proc) {
|
|
185
|
-
instances.delete(sessionId);
|
|
186
|
-
metrics.activeInstances = instances.size;
|
|
187
|
-
// Only recycle port on clean exit (code 0), not on errors
|
|
188
|
-
// Failed ttyd processes may leave ports in TIME_WAIT state
|
|
189
|
-
if (code === 0) {
|
|
190
|
-
availablePorts.add(port);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
recordWebsocketMetric({
|
|
194
|
-
metric: "websocket_disconnect",
|
|
195
|
-
outcome: code === 0 ? "success" : "failure",
|
|
196
|
-
sessionId,
|
|
197
|
-
reason: `ttyd_exit:${code}`,
|
|
198
|
-
data: { port },
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
proc.once("error", (err) => {
|
|
202
|
-
console.error(`[Terminal] ttyd ${sessionId} error:`, err.message);
|
|
203
|
-
// Only delete if this is still the current instance (prevents race with exit handler)
|
|
204
|
-
const current = instances.get(sessionId);
|
|
205
|
-
if (current?.process === proc) {
|
|
206
|
-
instances.delete(sessionId);
|
|
207
|
-
metrics.activeInstances = instances.size;
|
|
208
|
-
// Don't recycle port on error - may still be in use or TIME_WAIT
|
|
209
|
-
}
|
|
210
|
-
metrics.totalErrors += 1;
|
|
211
|
-
metrics.lastErrorAt = new Date().toISOString();
|
|
212
|
-
metrics.lastErrorReason = err.message;
|
|
213
|
-
recordWebsocketMetric({
|
|
214
|
-
metric: "websocket_error",
|
|
215
|
-
outcome: "failure",
|
|
216
|
-
sessionId,
|
|
217
|
-
reason: err.message,
|
|
218
|
-
data: { port },
|
|
219
|
-
});
|
|
220
|
-
// Kill any running process
|
|
221
|
-
try {
|
|
222
|
-
proc.kill();
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
// Ignore kill errors if process already dead
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
const instance = { sessionId, port, process: proc };
|
|
229
|
-
instances.set(sessionId, instance);
|
|
230
|
-
metrics.activeInstances = instances.size;
|
|
231
|
-
recordWebsocketMetric({
|
|
232
|
-
metric: "websocket_connect",
|
|
233
|
-
outcome: "success",
|
|
234
|
-
sessionId,
|
|
235
|
-
data: { reused: false, port },
|
|
236
|
-
});
|
|
237
|
-
return instance;
|
|
238
|
-
}
|
|
239
|
-
// Simple HTTP API for the dashboard to request terminal URLs
|
|
240
|
-
const server = createServer(async (req, res) => {
|
|
241
|
-
const url = new URL(req.url ?? "/", "http://localhost");
|
|
242
|
-
// CORS for dashboard - allow requests from the same host as the dashboard
|
|
243
|
-
// TODO: Replace with proper session-based authentication
|
|
244
|
-
const origin = req.headers.origin;
|
|
245
|
-
if (origin && origin !== "null") {
|
|
246
|
-
// Extract hostname from origin and compare with request host
|
|
247
|
-
try {
|
|
248
|
-
const originUrl = new URL(origin);
|
|
249
|
-
const requestHost = req.headers.host;
|
|
250
|
-
// Allow if origin hostname matches request host (supports remote deployments)
|
|
251
|
-
if (requestHost && originUrl.hostname === requestHost.split(":")[0]) {
|
|
252
|
-
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
catch {
|
|
256
|
-
// Invalid origin URL, don't set CORS header
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
// Allow null origin (file:// or local HTML files)
|
|
261
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
262
|
-
}
|
|
263
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
264
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
265
|
-
if (req.method === "OPTIONS") {
|
|
266
|
-
res.writeHead(204);
|
|
267
|
-
res.end();
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
// GET /terminal?session=ao-1 → returns { url, port }
|
|
271
|
-
if (url.pathname === "/terminal") {
|
|
272
|
-
const sessionId = url.searchParams.get("session");
|
|
273
|
-
if (!sessionId) {
|
|
274
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
275
|
-
res.end(JSON.stringify({ error: "Missing session parameter" }));
|
|
276
|
-
recordWebsocketMetric({
|
|
277
|
-
metric: "websocket_error",
|
|
278
|
-
outcome: "failure",
|
|
279
|
-
reason: "Missing session parameter",
|
|
280
|
-
});
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
// Validate session ID to prevent path traversal and injection
|
|
284
|
-
if (!validateSessionId(sessionId)) {
|
|
285
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
286
|
-
res.end(JSON.stringify({ error: "Invalid session ID" }));
|
|
287
|
-
recordWebsocketMetric({
|
|
288
|
-
metric: "websocket_error",
|
|
289
|
-
outcome: "failure",
|
|
290
|
-
sessionId,
|
|
291
|
-
reason: "Invalid session ID",
|
|
292
|
-
});
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
// Resolve tmux session name: try exact match first, then suffix match
|
|
296
|
-
// (hash-prefixed sessions like "8474d6f29887-ao-15" are accessed by user-facing ID "ao-15")
|
|
297
|
-
const tmuxSessionId = resolveTmuxSession(sessionId, TMUX);
|
|
298
|
-
if (!tmuxSessionId) {
|
|
299
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
300
|
-
res.end(JSON.stringify({ error: "Session not found" }));
|
|
301
|
-
recordWebsocketMetric({
|
|
302
|
-
metric: "websocket_error",
|
|
303
|
-
outcome: "failure",
|
|
304
|
-
sessionId,
|
|
305
|
-
reason: "Session not found",
|
|
306
|
-
});
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
// Spawn ttyd and wait for it to be ready (catch port exhaustion and startup failures)
|
|
310
|
-
try {
|
|
311
|
-
const instance = getOrSpawnTtyd(sessionId, tmuxSessionId);
|
|
312
|
-
await waitForTtyd(instance.port, sessionId);
|
|
313
|
-
// Use the request host to construct the terminal URL (supports remote access)
|
|
314
|
-
const host = req.headers.host ?? "localhost";
|
|
315
|
-
const protocol = req.headers["x-forwarded-proto"] ?? "http";
|
|
316
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
317
|
-
res.end(JSON.stringify({
|
|
318
|
-
url: `${protocol}://${host.split(":")[0]}:${instance.port}/${sessionId}/`,
|
|
319
|
-
port: instance.port,
|
|
320
|
-
sessionId,
|
|
321
|
-
}));
|
|
322
|
-
}
|
|
323
|
-
catch (err) {
|
|
324
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
325
|
-
console.error(`[Terminal] Failed to start terminal for ${sessionId}:`, errorMsg);
|
|
326
|
-
metrics.totalErrors += 1;
|
|
327
|
-
metrics.lastErrorAt = new Date().toISOString();
|
|
328
|
-
metrics.lastErrorReason = errorMsg;
|
|
329
|
-
recordWebsocketMetric({
|
|
330
|
-
metric: "websocket_error",
|
|
331
|
-
outcome: "failure",
|
|
332
|
-
sessionId,
|
|
333
|
-
reason: errorMsg,
|
|
334
|
-
});
|
|
335
|
-
res.writeHead(503, { "Content-Type": "application/json" });
|
|
336
|
-
res.end(JSON.stringify({ error: "Failed to start terminal" }));
|
|
337
|
-
}
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
// GET /health
|
|
341
|
-
if (url.pathname === "/health") {
|
|
342
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
343
|
-
res.end(JSON.stringify({
|
|
344
|
-
instances: Object.fromEntries([...instances.entries()].map(([id, inst]) => [id, { port: inst.port }])),
|
|
345
|
-
metrics,
|
|
346
|
-
}));
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
res.writeHead(404);
|
|
350
|
-
res.end("Not found");
|
|
351
|
-
});
|
|
352
|
-
const PORT = parseInt(process.env.TERMINAL_PORT ?? "14800", 10);
|
|
353
|
-
server.listen(PORT, () => {
|
|
354
|
-
console.log(`[Terminal] Server listening on port ${PORT}`);
|
|
355
|
-
});
|
|
356
|
-
// Graceful shutdown — kill all ttyd instances
|
|
357
|
-
function shutdown(signal) {
|
|
358
|
-
console.log(`[Terminal] Received ${signal}, shutting down...`);
|
|
359
|
-
for (const [, instance] of instances) {
|
|
360
|
-
instance.process.kill();
|
|
361
|
-
}
|
|
362
|
-
server.close(() => {
|
|
363
|
-
console.log("[Terminal] Server closed");
|
|
364
|
-
process.exit(0);
|
|
365
|
-
});
|
|
366
|
-
// Force exit after 5s if graceful shutdown hangs
|
|
367
|
-
// Use unref() so this timer doesn't prevent process exit if server closes quickly
|
|
368
|
-
const forceExitTimer = setTimeout(() => {
|
|
369
|
-
console.error("[Terminal] Forced shutdown after timeout");
|
|
370
|
-
process.exit(1);
|
|
371
|
-
}, 5000);
|
|
372
|
-
forceExitTimer.unref();
|
|
373
|
-
}
|
|
374
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
375
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
File without changes
|