@vibedeckx/linux-x64 0.1.10
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/dist/agent-provider.d.ts +92 -0
- package/dist/agent-provider.js +8 -0
- package/dist/agent-session-manager.d.ts +165 -0
- package/dist/agent-session-manager.js +985 -0
- package/dist/agent-types.d.ts +148 -0
- package/dist/agent-types.js +4 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +4 -0
- package/dist/browser-manager.d.ts +46 -0
- package/dist/browser-manager.js +182 -0
- package/dist/chat-session-manager.d.ts +101 -0
- package/dist/chat-session-manager.js +1425 -0
- package/dist/command.d.ts +1 -0
- package/dist/command.js +163 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +5 -0
- package/dist/conversation-patch.d.ts +103 -0
- package/dist/conversation-patch.js +69 -0
- package/dist/dialog.d.ts +1 -0
- package/dist/dialog.js +41 -0
- package/dist/entry-index-provider.d.ts +74 -0
- package/dist/entry-index-provider.js +105 -0
- package/dist/event-bus.d.ts +47 -0
- package/dist/event-bus.js +16 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/plugins/shared-services.d.ts +8 -0
- package/dist/plugins/shared-services.js +65 -0
- package/dist/process-manager.d.ts +146 -0
- package/dist/process-manager.js +779 -0
- package/dist/providers/claude-code-provider.d.ts +13 -0
- package/dist/providers/claude-code-provider.js +127 -0
- package/dist/providers/codex-provider.d.ts +41 -0
- package/dist/providers/codex-provider.js +354 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.js +19 -0
- package/dist/remote-patch-cache.d.ts +67 -0
- package/dist/remote-patch-cache.js +190 -0
- package/dist/reverse-connect-client.d.ts +27 -0
- package/dist/reverse-connect-client.js +255 -0
- package/dist/reverse-connect-manager.d.ts +31 -0
- package/dist/reverse-connect-manager.js +287 -0
- package/dist/reverse-connect-types.d.ts +55 -0
- package/dist/reverse-connect-types.js +7 -0
- package/dist/routes/agent-session-routes.d.ts +4 -0
- package/dist/routes/agent-session-routes.js +442 -0
- package/dist/routes/browser-proxy-routes.d.ts +25 -0
- package/dist/routes/browser-proxy-routes.js +421 -0
- package/dist/routes/browser-routes.d.ts +4 -0
- package/dist/routes/browser-routes.js +73 -0
- package/dist/routes/chat-session-routes.d.ts +7 -0
- package/dist/routes/chat-session-routes.js +69 -0
- package/dist/routes/diff-routes.d.ts +4 -0
- package/dist/routes/diff-routes.js +208 -0
- package/dist/routes/event-routes.d.ts +4 -0
- package/dist/routes/event-routes.js +52 -0
- package/dist/routes/executor-group-routes.d.ts +4 -0
- package/dist/routes/executor-group-routes.js +76 -0
- package/dist/routes/executor-routes.d.ts +4 -0
- package/dist/routes/executor-routes.js +106 -0
- package/dist/routes/file-routes.d.ts +4 -0
- package/dist/routes/file-routes.js +331 -0
- package/dist/routes/process-routes.d.ts +4 -0
- package/dist/routes/process-routes.js +183 -0
- package/dist/routes/project-remote-routes.d.ts +4 -0
- package/dist/routes/project-remote-routes.js +82 -0
- package/dist/routes/project-routes.d.ts +4 -0
- package/dist/routes/project-routes.js +286 -0
- package/dist/routes/remote-routes.d.ts +4 -0
- package/dist/routes/remote-routes.js +59 -0
- package/dist/routes/remote-server-routes.d.ts +4 -0
- package/dist/routes/remote-server-routes.js +157 -0
- package/dist/routes/reverse-connect-routes.d.ts +4 -0
- package/dist/routes/reverse-connect-routes.js +33 -0
- package/dist/routes/settings-routes.d.ts +4 -0
- package/dist/routes/settings-routes.js +129 -0
- package/dist/routes/task-routes.d.ts +4 -0
- package/dist/routes/task-routes.js +107 -0
- package/dist/routes/terminal-routes.d.ts +4 -0
- package/dist/routes/terminal-routes.js +187 -0
- package/dist/routes/translate-routes.d.ts +4 -0
- package/dist/routes/translate-routes.js +38 -0
- package/dist/routes/websocket-routes.d.ts +4 -0
- package/dist/routes/websocket-routes.js +604 -0
- package/dist/routes/worktree-routes.d.ts +4 -0
- package/dist/routes/worktree-routes.js +511 -0
- package/dist/server-types.d.ts +41 -0
- package/dist/server-types.js +1 -0
- package/dist/server.d.ts +23 -0
- package/dist/server.js +238 -0
- package/dist/storage/sqlite.d.ts +2 -0
- package/dist/storage/sqlite.js +1073 -0
- package/dist/storage/types.d.ts +281 -0
- package/dist/storage/types.js +1 -0
- package/dist/ui/404/index.html +1 -0
- package/dist/ui/404.html +1 -0
- package/dist/ui/__next.__PAGE__.txt +10 -0
- package/dist/ui/__next._full.txt +25 -0
- package/dist/ui/__next._head.txt +6 -0
- package/dist/ui/__next._index.txt +9 -0
- package/dist/ui/__next._tree.txt +6 -0
- package/dist/ui/_next/static/7vIIuOQPJu8rdb6uAhwtg/_buildManifest.js +11 -0
- package/dist/ui/_next/static/7vIIuOQPJu8rdb6uAhwtg/_clientMiddlewareManifest.json +1 -0
- package/dist/ui/_next/static/7vIIuOQPJu8rdb6uAhwtg/_ssgManifest.js +1 -0
- package/dist/ui/_next/static/chunks/002f5a047b8d07d4.js +1 -0
- package/dist/ui/_next/static/chunks/01178e167ba3e5b4.js +1 -0
- package/dist/ui/_next/static/chunks/020d675d21be28d4.js +1 -0
- package/dist/ui/_next/static/chunks/024926197424b4a5.js +1 -0
- package/dist/ui/_next/static/chunks/02c93f6ca211a65d.js +1 -0
- package/dist/ui/_next/static/chunks/0364437dee56dc96.js +1 -0
- package/dist/ui/_next/static/chunks/03fc6b527b16efdc.js +1 -0
- package/dist/ui/_next/static/chunks/04f9a7932751cf2d.js +1 -0
- package/dist/ui/_next/static/chunks/0624b8204e5ae457.js +136 -0
- package/dist/ui/_next/static/chunks/067d8978cf41b901.js +1 -0
- package/dist/ui/_next/static/chunks/06d96238e85cdbb3.js +1 -0
- package/dist/ui/_next/static/chunks/071b9575dbdf1dcb.js +1 -0
- package/dist/ui/_next/static/chunks/073d5b9dc87ceab0.js +1 -0
- package/dist/ui/_next/static/chunks/0876a7111934d6f7.js +1 -0
- package/dist/ui/_next/static/chunks/08ffc3bf406c665d.js +1 -0
- package/dist/ui/_next/static/chunks/0969e8274c92c2d8.js +1 -0
- package/dist/ui/_next/static/chunks/0a3f62f00d7bec78.js +1 -0
- package/dist/ui/_next/static/chunks/0b80166023d89049.js +5 -0
- package/dist/ui/_next/static/chunks/0c2a941e61c395b6.js +1 -0
- package/dist/ui/_next/static/chunks/0c5eac8493334420.js +1 -0
- package/dist/ui/_next/static/chunks/0cc61d37b2333469.js +1 -0
- package/dist/ui/_next/static/chunks/0d440843348b2871.js +1 -0
- package/dist/ui/_next/static/chunks/0da3d15845f17208.js +1 -0
- package/dist/ui/_next/static/chunks/0e1982b1a6cbd127.js +53 -0
- package/dist/ui/_next/static/chunks/0e81d5bc1c725b75.js +1 -0
- package/dist/ui/_next/static/chunks/0e862e51b01e904b.js +63 -0
- package/dist/ui/_next/static/chunks/0ed7b6d86744b723.js +1 -0
- package/dist/ui/_next/static/chunks/0f0b3025f4e268b1.js +1 -0
- package/dist/ui/_next/static/chunks/10a30eb52825da36.js +1 -0
- package/dist/ui/_next/static/chunks/10c48da576fd8eef.js +1 -0
- package/dist/ui/_next/static/chunks/133e9f1435ca5f45.js +29 -0
- package/dist/ui/_next/static/chunks/16f4db3a54f167fd.js +1 -0
- package/dist/ui/_next/static/chunks/1988e3ecf5ad06d3.js +1 -0
- package/dist/ui/_next/static/chunks/1acfae010fd77014.js +152 -0
- package/dist/ui/_next/static/chunks/1ae53c2f1fff8cc2.js +1 -0
- package/dist/ui/_next/static/chunks/1af4fbc6c1256fae.js +1 -0
- package/dist/ui/_next/static/chunks/1b758c732032b236.js +1 -0
- package/dist/ui/_next/static/chunks/1ba26455a9f70c08.js +1 -0
- package/dist/ui/_next/static/chunks/1d22644cb4049d6b.css +1 -0
- package/dist/ui/_next/static/chunks/1d241f3b57c14cf7.js +1 -0
- package/dist/ui/_next/static/chunks/1d640bbe1c2c7869.js +1 -0
- package/dist/ui/_next/static/chunks/1da6354c9cb6f0de.js +1 -0
- package/dist/ui/_next/static/chunks/1dcd49914412f67b.js +1 -0
- package/dist/ui/_next/static/chunks/1e3b3771294825a4.js +1 -0
- package/dist/ui/_next/static/chunks/1eaa8ad7eca7f957.js +1 -0
- package/dist/ui/_next/static/chunks/1ebc88f60cadb128.js +1 -0
- package/dist/ui/_next/static/chunks/1ecb57b258088259.js +1 -0
- package/dist/ui/_next/static/chunks/1f5ba6b80fe19200.js +1 -0
- package/dist/ui/_next/static/chunks/20a5531534828366.js +1 -0
- package/dist/ui/_next/static/chunks/20b511fc299dfe9a.js +1 -0
- package/dist/ui/_next/static/chunks/20bff4eb9a0a6872.css +1 -0
- package/dist/ui/_next/static/chunks/219b7d9e437c6bd8.js +15 -0
- package/dist/ui/_next/static/chunks/21b32f530e0df2c5.js +1 -0
- package/dist/ui/_next/static/chunks/21fc6dc60e7647b1.js +1 -0
- package/dist/ui/_next/static/chunks/221a27e65aa5fbfd.js +1 -0
- package/dist/ui/_next/static/chunks/23a1e247c32bcc61.js +93 -0
- package/dist/ui/_next/static/chunks/2473c16c0c2f6b5f.css +2 -0
- package/dist/ui/_next/static/chunks/2615c71c0f8fe9bd.js +1 -0
- package/dist/ui/_next/static/chunks/263327288d5e2703.js +1 -0
- package/dist/ui/_next/static/chunks/2663fbaf43239e38.js +1 -0
- package/dist/ui/_next/static/chunks/27332c590d59f4e5.js +1 -0
- package/dist/ui/_next/static/chunks/27ca4a4e8191093f.js +1 -0
- package/dist/ui/_next/static/chunks/29b7618dcaa8edba.js +1 -0
- package/dist/ui/_next/static/chunks/2ba1d2b55b82f4da.js +21 -0
- package/dist/ui/_next/static/chunks/2cb23686e72468c8.js +1 -0
- package/dist/ui/_next/static/chunks/2d46f05dcbf1cbc2.js +1 -0
- package/dist/ui/_next/static/chunks/2d4c0fd06ca34510.js +2 -0
- package/dist/ui/_next/static/chunks/2f3d1d07474b8f79.js +1 -0
- package/dist/ui/_next/static/chunks/2f85c2849249a0dd.js +1 -0
- package/dist/ui/_next/static/chunks/3001d378f166eec9.js +1 -0
- package/dist/ui/_next/static/chunks/3018714f3827e360.js +1 -0
- package/dist/ui/_next/static/chunks/301cc25e0d489351.js +1 -0
- package/dist/ui/_next/static/chunks/305242b22ba8b49b.js +1 -0
- package/dist/ui/_next/static/chunks/3067c6e369066bd6.js +1 -0
- package/dist/ui/_next/static/chunks/311a77c9d5cb9de9.js +1 -0
- package/dist/ui/_next/static/chunks/320c001380e81470.js +1 -0
- package/dist/ui/_next/static/chunks/329db6c551df0faf.js +1 -0
- package/dist/ui/_next/static/chunks/33da178724072b3d.js +1 -0
- package/dist/ui/_next/static/chunks/33e8248c9296537a.js +1 -0
- package/dist/ui/_next/static/chunks/35a9992a8958f93b.js +1 -0
- package/dist/ui/_next/static/chunks/35bb90cf09892b72.js +1 -0
- package/dist/ui/_next/static/chunks/379f91b92366dc15.js +1 -0
- package/dist/ui/_next/static/chunks/391f22359769763f.js +1 -0
- package/dist/ui/_next/static/chunks/39231cb1044f7823.js +1 -0
- package/dist/ui/_next/static/chunks/394e8b7a1c2c58c6.js +1 -0
- package/dist/ui/_next/static/chunks/3a2cfdeb5f76ebd2.js +1 -0
- package/dist/ui/_next/static/chunks/3a3bd015fd042386.js +1 -0
- package/dist/ui/_next/static/chunks/3ad1bee238af9b5a.js +1 -0
- package/dist/ui/_next/static/chunks/3b2b2f7a9b7b130d.js +1 -0
- package/dist/ui/_next/static/chunks/3b586f80547e3a22.js +1 -0
- package/dist/ui/_next/static/chunks/3ca412e72bd3707a.js +1 -0
- package/dist/ui/_next/static/chunks/3cbb3bdceb4230af.js +1 -0
- package/dist/ui/_next/static/chunks/3ed1465109fecc2d.js +1 -0
- package/dist/ui/_next/static/chunks/3fd0801238b3b099.js +1 -0
- package/dist/ui/_next/static/chunks/401df66bd5da2115.js +15 -0
- package/dist/ui/_next/static/chunks/4211f4efe510f7ac.js +1 -0
- package/dist/ui/_next/static/chunks/43085364d0a41d1d.js +1 -0
- package/dist/ui/_next/static/chunks/4310c821dbee7a16.js +1 -0
- package/dist/ui/_next/static/chunks/46382f31f63e59cf.js +1 -0
- package/dist/ui/_next/static/chunks/468836b90ddf24d6.js +1 -0
- package/dist/ui/_next/static/chunks/470d091143104517.js +1 -0
- package/dist/ui/_next/static/chunks/4712e4f7e6b6ddc1.js +1 -0
- package/dist/ui/_next/static/chunks/4899f1e3f21c077e.js +1 -0
- package/dist/ui/_next/static/chunks/492ce6930bf61811.js +1 -0
- package/dist/ui/_next/static/chunks/494485a20952ffa3.js +1 -0
- package/dist/ui/_next/static/chunks/49535db309898f43.js +1 -0
- package/dist/ui/_next/static/chunks/4b8d2612d69e2013.js +1 -0
- package/dist/ui/_next/static/chunks/4c4a0f67891826a3.js +1 -0
- package/dist/ui/_next/static/chunks/4cc11ce32f4453b0.js +1 -0
- package/dist/ui/_next/static/chunks/4d03a0bc963fc3d4.js +1 -0
- package/dist/ui/_next/static/chunks/4d603a66c067134e.js +1 -0
- package/dist/ui/_next/static/chunks/4d8d7e62c2743f71.js +15 -0
- package/dist/ui/_next/static/chunks/4da42f10a5460b36.js +1 -0
- package/dist/ui/_next/static/chunks/4e832ffb65e75807.js +1 -0
- package/dist/ui/_next/static/chunks/4e954e1cec89a9ea.js +1 -0
- package/dist/ui/_next/static/chunks/4f2bc7a7a6b05a8b.js +55 -0
- package/dist/ui/_next/static/chunks/4f9c934abf34ceb9.js +1 -0
- package/dist/ui/_next/static/chunks/4fa248b0d2586928.js +1 -0
- package/dist/ui/_next/static/chunks/4fafbac2156844ca.js +1 -0
- package/dist/ui/_next/static/chunks/502039e483cc5e48.js +1 -0
- package/dist/ui/_next/static/chunks/509c91c38224448a.js +1 -0
- package/dist/ui/_next/static/chunks/5159d6f8d4307f36.js +1 -0
- package/dist/ui/_next/static/chunks/5179ab56aaaed42d.js +1 -0
- package/dist/ui/_next/static/chunks/5269ea07faff562d.js +1 -0
- package/dist/ui/_next/static/chunks/529f3f0f7d42444a.js +1 -0
- package/dist/ui/_next/static/chunks/5317db6783ee8dc9.js +1 -0
- package/dist/ui/_next/static/chunks/53a7b7c0ab020902.js +1 -0
- package/dist/ui/_next/static/chunks/544869c670c1dd8e.js +1 -0
- package/dist/ui/_next/static/chunks/547c0db6a433370e.js +1 -0
- package/dist/ui/_next/static/chunks/54d5670f5fa2abbe.css +1 -0
- package/dist/ui/_next/static/chunks/55761e35a8946a1d.js +15 -0
- package/dist/ui/_next/static/chunks/558f73c16b7ff14f.js +1 -0
- package/dist/ui/_next/static/chunks/55c76f605958d671.js +1 -0
- package/dist/ui/_next/static/chunks/55dc6750fb117bf9.js +1 -0
- package/dist/ui/_next/static/chunks/584bad9cf2498405.js +1 -0
- package/dist/ui/_next/static/chunks/593b85b9abea3ea6.js +1 -0
- package/dist/ui/_next/static/chunks/5a79af73c96155d4.js +1 -0
- package/dist/ui/_next/static/chunks/5ee54b9b6b400134.js +1 -0
- package/dist/ui/_next/static/chunks/5f27ee48dc820109.js +1 -0
- package/dist/ui/_next/static/chunks/5ffd50a08d82e2f3.js +1 -0
- package/dist/ui/_next/static/chunks/61d65fe807f69413.js +62 -0
- package/dist/ui/_next/static/chunks/626a650bcaaecdb8.js +1 -0
- package/dist/ui/_next/static/chunks/62baecafed4dbced.js +1 -0
- package/dist/ui/_next/static/chunks/634a4b5b6a38ccab.js +1 -0
- package/dist/ui/_next/static/chunks/643c359cf3f7364e.js +1 -0
- package/dist/ui/_next/static/chunks/649e65f4820a772b.js +1 -0
- package/dist/ui/_next/static/chunks/65028938188a230c.js +1 -0
- package/dist/ui/_next/static/chunks/65b3658348e8d4fd.js +1 -0
- package/dist/ui/_next/static/chunks/65eed220466cbdbc.js +1 -0
- package/dist/ui/_next/static/chunks/66714687cfd91953.js +1 -0
- package/dist/ui/_next/static/chunks/66ccaa3e69ed7a69.js +1 -0
- package/dist/ui/_next/static/chunks/6754600af0c6b3a8.js +1 -0
- package/dist/ui/_next/static/chunks/67a1d37727697340.js +1 -0
- package/dist/ui/_next/static/chunks/67e965a7f9531cee.js +1 -0
- package/dist/ui/_next/static/chunks/6816bf02e4c22a55.js +1 -0
- package/dist/ui/_next/static/chunks/68621b90909ec4e6.js +1 -0
- package/dist/ui/_next/static/chunks/6929ceb718c6e4c6.js +1 -0
- package/dist/ui/_next/static/chunks/69362fff2240a17b.js +1 -0
- package/dist/ui/_next/static/chunks/69dab47a307b1a37.js +1 -0
- package/dist/ui/_next/static/chunks/6afa71c8b3358dd5.js +1 -0
- package/dist/ui/_next/static/chunks/6bf54dc328e667f7.js +1 -0
- package/dist/ui/_next/static/chunks/6c44508faf13f6f0.js +1 -0
- package/dist/ui/_next/static/chunks/6c9c2b61c905a2de.js +1 -0
- package/dist/ui/_next/static/chunks/6cc2e3d7873522b9.js +1 -0
- package/dist/ui/_next/static/chunks/6d01e8902e85bfe0.js +1 -0
- package/dist/ui/_next/static/chunks/6dc69e4a91f7a353.js +1 -0
- package/dist/ui/_next/static/chunks/6e832e016b60ae19.js +1 -0
- package/dist/ui/_next/static/chunks/6eab0d8815c18a6d.js +1 -0
- package/dist/ui/_next/static/chunks/6f862eb588fa3b7e.js +1 -0
- package/dist/ui/_next/static/chunks/6ff7b0a8653036b2.js +1 -0
- package/dist/ui/_next/static/chunks/710ce144a9645f3c.js +1 -0
- package/dist/ui/_next/static/chunks/71293e300c639b6b.js +1 -0
- package/dist/ui/_next/static/chunks/7186d7cce354a012.js +1 -0
- package/dist/ui/_next/static/chunks/7215d586009e8158.js +1 -0
- package/dist/ui/_next/static/chunks/726fdeaff53a89ac.js +1 -0
- package/dist/ui/_next/static/chunks/7322a00d61e7ffad.js +1 -0
- package/dist/ui/_next/static/chunks/73c60ee9f233051d.js +1 -0
- package/dist/ui/_next/static/chunks/740f5627b5c57baa.js +1 -0
- package/dist/ui/_next/static/chunks/74438794a8e9ba80.js +1 -0
- package/dist/ui/_next/static/chunks/74ffcd7d13b3fea0.js +1 -0
- package/dist/ui/_next/static/chunks/7510496048ab1ad4.js +1 -0
- package/dist/ui/_next/static/chunks/7636f89f38cbc3e3.js +1 -0
- package/dist/ui/_next/static/chunks/782a93ec4348b666.js +1 -0
- package/dist/ui/_next/static/chunks/7850bbac1925646c.js +1 -0
- package/dist/ui/_next/static/chunks/790dcc1e825d2504.js +1 -0
- package/dist/ui/_next/static/chunks/798597a1c1248d29.js +1 -0
- package/dist/ui/_next/static/chunks/7a70641f70a5c72d.js +60 -0
- package/dist/ui/_next/static/chunks/7c62789391c35dce.js +1 -0
- package/dist/ui/_next/static/chunks/7d533d3f2ab624f2.js +1 -0
- package/dist/ui/_next/static/chunks/7d6087c3fabf9ded.js +1 -0
- package/dist/ui/_next/static/chunks/7d84c9cb810e6902.js +1 -0
- package/dist/ui/_next/static/chunks/7dc4f5ba8c25c409.js +1 -0
- package/dist/ui/_next/static/chunks/7e10c0644fbb99d1.js +1 -0
- package/dist/ui/_next/static/chunks/7e7aaacf104c17f4.js +1 -0
- package/dist/ui/_next/static/chunks/7e92979509de57ed.js +1 -0
- package/dist/ui/_next/static/chunks/7f9a52e36f9f001e.js +1 -0
- package/dist/ui/_next/static/chunks/7fc4ae2e7cf2e37e.js +1 -0
- package/dist/ui/_next/static/chunks/801232fdde8ce252.js +1 -0
- package/dist/ui/_next/static/chunks/808ed0f189237446.js +1 -0
- package/dist/ui/_next/static/chunks/8199e6b3fa54b2ff.js +1 -0
- package/dist/ui/_next/static/chunks/81fa0fa05a94eb02.js +1 -0
- package/dist/ui/_next/static/chunks/821b5389566b82d2.js +1 -0
- package/dist/ui/_next/static/chunks/82b9e04f88e657df.js +1 -0
- package/dist/ui/_next/static/chunks/830c79cc12f2c5a4.js +1 -0
- package/dist/ui/_next/static/chunks/8388ae863590404b.js +4 -0
- package/dist/ui/_next/static/chunks/83ab70f11a82a8c6.js +1 -0
- package/dist/ui/_next/static/chunks/83faf7904ac18d7f.js +1 -0
- package/dist/ui/_next/static/chunks/850d850bd879606f.js +1 -0
- package/dist/ui/_next/static/chunks/864d4b5cf4ae2226.js +1 -0
- package/dist/ui/_next/static/chunks/86d8962196053f53.js +139 -0
- package/dist/ui/_next/static/chunks/8723d7000b263475.js +1 -0
- package/dist/ui/_next/static/chunks/87e5597a4336e2c1.js +1 -0
- package/dist/ui/_next/static/chunks/899fe56c4c707c65.js +1 -0
- package/dist/ui/_next/static/chunks/8bda52f55493ae9b.js +1 -0
- package/dist/ui/_next/static/chunks/8ce5668c3da0679a.js +1 -0
- package/dist/ui/_next/static/chunks/8d1c22aafb4783c5.js +1 -0
- package/dist/ui/_next/static/chunks/8deda0adfe811d18.js +1 -0
- package/dist/ui/_next/static/chunks/8eed597098a27801.js +1 -0
- package/dist/ui/_next/static/chunks/8ff7a1947b8d114b.js +1 -0
- package/dist/ui/_next/static/chunks/90367a2ee1e720b2.js +1 -0
- package/dist/ui/_next/static/chunks/9097640e1931c0de.js +1 -0
- package/dist/ui/_next/static/chunks/912457473a4c91c3.js +1 -0
- package/dist/ui/_next/static/chunks/91f7dd388c6b88c6.js +1 -0
- package/dist/ui/_next/static/chunks/932d4334e5f98b7c.js +1 -0
- package/dist/ui/_next/static/chunks/94b1efc14d44707b.js +1 -0
- package/dist/ui/_next/static/chunks/956107bc3ba52911.js +1 -0
- package/dist/ui/_next/static/chunks/962e5cad8ea716fc.js +36 -0
- package/dist/ui/_next/static/chunks/96acf903dff8ecf7.js +1 -0
- package/dist/ui/_next/static/chunks/96b274c82c194b4f.js +1 -0
- package/dist/ui/_next/static/chunks/96b3c1b1afe3201d.js +1 -0
- package/dist/ui/_next/static/chunks/9880c26551206d33.js +1 -0
- package/dist/ui/_next/static/chunks/98ea228d01556617.js +1 -0
- package/dist/ui/_next/static/chunks/9951c25a8b05148f.js +1 -0
- package/dist/ui/_next/static/chunks/9b10895d33be5f6e.js +1 -0
- package/dist/ui/_next/static/chunks/9b50a76ddbe4934d.js +1 -0
- package/dist/ui/_next/static/chunks/9bbb1c0146042008.js +1 -0
- package/dist/ui/_next/static/chunks/9ccab70823e99451.js +1 -0
- package/dist/ui/_next/static/chunks/9cd7bcb29e0b3418.js +24 -0
- package/dist/ui/_next/static/chunks/9d06bd5d701ace73.js +1 -0
- package/dist/ui/_next/static/chunks/9e5d9fc30c12fbdd.js +1 -0
- package/dist/ui/_next/static/chunks/9ec3bc5544bcc15a.js +1 -0
- package/dist/ui/_next/static/chunks/9f47133184a7455a.js +1 -0
- package/dist/ui/_next/static/chunks/a002d3850b7c7c59.js +1 -0
- package/dist/ui/_next/static/chunks/a0bc5573cadd1377.js +1 -0
- package/dist/ui/_next/static/chunks/a1d3de9e7615662e.js +160 -0
- package/dist/ui/_next/static/chunks/a2ce60768a8373e7.js +1 -0
- package/dist/ui/_next/static/chunks/a305087b1a55b367.js +1 -0
- package/dist/ui/_next/static/chunks/a41df0d443f66c68.js +1 -0
- package/dist/ui/_next/static/chunks/a49d2e9f31886b5e.js +1 -0
- package/dist/ui/_next/static/chunks/a50922e0883f65cd.js +1 -0
- package/dist/ui/_next/static/chunks/a60e84ee8b6c5ffc.js +43 -0
- package/dist/ui/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- package/dist/ui/_next/static/chunks/a6dad97d9634a72d.js.map +1 -0
- package/dist/ui/_next/static/chunks/a7c40c289b5e2384.js +1 -0
- package/dist/ui/_next/static/chunks/a7fdfeea5fd894c1.js +59 -0
- package/dist/ui/_next/static/chunks/a81381241f4f484c.js +1 -0
- package/dist/ui/_next/static/chunks/a8a3989305bcc136.js +215 -0
- package/dist/ui/_next/static/chunks/a8cf26088e63128c.js +1 -0
- package/dist/ui/_next/static/chunks/aac4ca816d9ccdb6.js +1 -0
- package/dist/ui/_next/static/chunks/ab432bc28a971c8f.js +1 -0
- package/dist/ui/_next/static/chunks/abccff1b09b6effa.js +1 -0
- package/dist/ui/_next/static/chunks/ac8a8986f09ec520.js +1 -0
- package/dist/ui/_next/static/chunks/accbf306b5ddb732.js +1 -0
- package/dist/ui/_next/static/chunks/af7b09aba1d477ff.js +1 -0
- package/dist/ui/_next/static/chunks/af8114430894d79e.js +1 -0
- package/dist/ui/_next/static/chunks/afe2ff5d0727a240.js +1 -0
- package/dist/ui/_next/static/chunks/b096fa008120f0ae.js +1 -0
- package/dist/ui/_next/static/chunks/b0bc568eaff56dad.js +1 -0
- package/dist/ui/_next/static/chunks/b225dddc0852f85c.js +215 -0
- package/dist/ui/_next/static/chunks/b2342c5099957971.js +1 -0
- package/dist/ui/_next/static/chunks/b2d8c64d7d6a06b7.js +1 -0
- package/dist/ui/_next/static/chunks/b331c855cfc8fd57.js +1 -0
- package/dist/ui/_next/static/chunks/b356ee8615740392.js +1 -0
- package/dist/ui/_next/static/chunks/b3beaac9b7957d3f.js +1 -0
- package/dist/ui/_next/static/chunks/b4e7d4a109c4c080.js +1 -0
- package/dist/ui/_next/static/chunks/b5010f2ea9df9d00.js +77 -0
- package/dist/ui/_next/static/chunks/b5f9d21bd1b51a48.js +1 -0
- package/dist/ui/_next/static/chunks/b6a16eb77c5c9831.js +1 -0
- package/dist/ui/_next/static/chunks/b6a8e7c2216683ca.js +262 -0
- package/dist/ui/_next/static/chunks/b873c4d327a450e0.js +1 -0
- package/dist/ui/_next/static/chunks/b8aec8caed569fd2.js +24 -0
- package/dist/ui/_next/static/chunks/b8e7601e2379fd74.js +1 -0
- package/dist/ui/_next/static/chunks/ba3d03bf2f33f2a3.js +1 -0
- package/dist/ui/_next/static/chunks/ba698805336b2cb2.js +1 -0
- package/dist/ui/_next/static/chunks/bc48735eee7d1345.js +1 -0
- package/dist/ui/_next/static/chunks/bcb48aab1bdae96a.js +1 -0
- package/dist/ui/_next/static/chunks/bde2ca9d150d96ff.js +1 -0
- package/dist/ui/_next/static/chunks/be4af851547fc916.js +1 -0
- package/dist/ui/_next/static/chunks/be70ba8c4ba5cdb6.js +1 -0
- package/dist/ui/_next/static/chunks/be87578ee895734b.js +1 -0
- package/dist/ui/_next/static/chunks/c0aeb40fcca7d006.js +1 -0
- package/dist/ui/_next/static/chunks/c32156843c32ebca.js +1 -0
- package/dist/ui/_next/static/chunks/c57a3e045f3722c1.js +1 -0
- package/dist/ui/_next/static/chunks/c5add23cde4a234a.js +1 -0
- package/dist/ui/_next/static/chunks/c60897f3554a9388.js +1 -0
- package/dist/ui/_next/static/chunks/c648a36722afd12a.js +1 -0
- package/dist/ui/_next/static/chunks/c652f05e0a0d7b81.js +1 -0
- package/dist/ui/_next/static/chunks/c678d9303ed453b4.js +1 -0
- package/dist/ui/_next/static/chunks/c681430c24597d06.js +1 -0
- package/dist/ui/_next/static/chunks/c890870fb65940bc.js +1 -0
- package/dist/ui/_next/static/chunks/c94c394aeec71e21.js +1 -0
- package/dist/ui/_next/static/chunks/ca1eb1e2978389e4.js +1 -0
- package/dist/ui/_next/static/chunks/ca3512025a981c82.js +1 -0
- package/dist/ui/_next/static/chunks/ca6db0d999bad46b.js +1 -0
- package/dist/ui/_next/static/chunks/ca926babdf75597c.js +1 -0
- package/dist/ui/_next/static/chunks/cb0877764d4a31f2.js +1 -0
- package/dist/ui/_next/static/chunks/cb15e0c2ff49cf52.js +1 -0
- package/dist/ui/_next/static/chunks/cb1ae84204260ecd.js +1 -0
- package/dist/ui/_next/static/chunks/cb2ef733c53d80f7.js +1 -0
- package/dist/ui/_next/static/chunks/cbaa87ba8930fb75.js +1 -0
- package/dist/ui/_next/static/chunks/cbd29ba61906e19f.js +1 -0
- package/dist/ui/_next/static/chunks/ccf1e618faea3d02.js +148 -0
- package/dist/ui/_next/static/chunks/cd91712ea04f43ba.js +13 -0
- package/dist/ui/_next/static/chunks/cdd85dc039d450f3.js +1 -0
- package/dist/ui/_next/static/chunks/ce663833f73b1ec5.js +1 -0
- package/dist/ui/_next/static/chunks/ceda8fcac21d8e70.js +1 -0
- package/dist/ui/_next/static/chunks/cf565875b66f8cad.js +1 -0
- package/dist/ui/_next/static/chunks/d127ded39a594c84.js +1 -0
- package/dist/ui/_next/static/chunks/d34124988f5b8f6d.js +1 -0
- package/dist/ui/_next/static/chunks/d36807add3e11d59.js +60 -0
- package/dist/ui/_next/static/chunks/d3f2e4603faebed8.js +1 -0
- package/dist/ui/_next/static/chunks/d414302c23047b9b.js +1 -0
- package/dist/ui/_next/static/chunks/d42adc0237103e65.js +1 -0
- package/dist/ui/_next/static/chunks/d4a06b205f0a641b.js +1 -0
- package/dist/ui/_next/static/chunks/d4e5289ace3acef0.js +1 -0
- package/dist/ui/_next/static/chunks/d53b4725d3328076.js +1 -0
- package/dist/ui/_next/static/chunks/d5bd04634f922925.js +1 -0
- package/dist/ui/_next/static/chunks/d642c9229900dc48.js +1 -0
- package/dist/ui/_next/static/chunks/d6651bb78c09d144.js +5 -0
- package/dist/ui/_next/static/chunks/d7276c56aa62ceed.js +1 -0
- package/dist/ui/_next/static/chunks/d85cd9cda46f2d1b.js +1 -0
- package/dist/ui/_next/static/chunks/d8978f4b468bbad4.js +1 -0
- package/dist/ui/_next/static/chunks/d8fb9668a83c0603.js +1 -0
- package/dist/ui/_next/static/chunks/d95c62472fc41baf.js +1 -0
- package/dist/ui/_next/static/chunks/da13170b983f7ebf.js +1 -0
- package/dist/ui/_next/static/chunks/dae86e12c7741e6c.js +19 -0
- package/dist/ui/_next/static/chunks/db44023a744297d1.js +1 -0
- package/dist/ui/_next/static/chunks/dbe7840308be36b8.js +1 -0
- package/dist/ui/_next/static/chunks/dc0a4df8f7080b29.js +1 -0
- package/dist/ui/_next/static/chunks/dc4a6f59be156511.js +1 -0
- package/dist/ui/_next/static/chunks/dcda5e7b57ed88e0.js +1 -0
- package/dist/ui/_next/static/chunks/de9ff8c97f75b947.js +1 -0
- package/dist/ui/_next/static/chunks/df6abcdb3eb0b236.js +1 -0
- package/dist/ui/_next/static/chunks/df8a0933ca385a63.js +1 -0
- package/dist/ui/_next/static/chunks/dff6434c44c2dfaa.js +1 -0
- package/dist/ui/_next/static/chunks/e037ec5e62b2bdc3.js +1 -0
- package/dist/ui/_next/static/chunks/e212cf1f5a503899.js +1 -0
- package/dist/ui/_next/static/chunks/e2bfdac0a2305cc1.js +1 -0
- package/dist/ui/_next/static/chunks/e2dce648399ad4d4.js +1 -0
- package/dist/ui/_next/static/chunks/e318291bb8d74aee.js +1 -0
- package/dist/ui/_next/static/chunks/e33e40c2e7ab7629.js +1 -0
- package/dist/ui/_next/static/chunks/e410dfbcccddc478.js +1 -0
- package/dist/ui/_next/static/chunks/e411d58b52ab342b.js +1 -0
- package/dist/ui/_next/static/chunks/e57f7ffd0803a922.js +117 -0
- package/dist/ui/_next/static/chunks/e6b56ceb762e84c4.js +1 -0
- package/dist/ui/_next/static/chunks/e6d9a6ca68017eef.js +1 -0
- package/dist/ui/_next/static/chunks/e6ec2663605cf5a7.js +29 -0
- package/dist/ui/_next/static/chunks/e834330589be0639.js +1 -0
- package/dist/ui/_next/static/chunks/e8f662ba8bc76802.js +1 -0
- package/dist/ui/_next/static/chunks/e9e8e72d7ff45812.js +1 -0
- package/dist/ui/_next/static/chunks/eadd25eb66104a63.js +1 -0
- package/dist/ui/_next/static/chunks/ec3a7a7f48a8aca9.js +1 -0
- package/dist/ui/_next/static/chunks/ed62e349fb1ce14c.js +1 -0
- package/dist/ui/_next/static/chunks/ed6caa113f5769a5.js +1 -0
- package/dist/ui/_next/static/chunks/f02ac565bb5bc450.js +1 -0
- package/dist/ui/_next/static/chunks/f1a7a173e3da4ff4.js +1 -0
- package/dist/ui/_next/static/chunks/f268093817d3b260.js +1 -0
- package/dist/ui/_next/static/chunks/f6f811ccfe79e963.js +1 -0
- package/dist/ui/_next/static/chunks/f8955634331fd956.js +1 -0
- package/dist/ui/_next/static/chunks/f8c965a996875e30.js +56 -0
- package/dist/ui/_next/static/chunks/f9bb8411d37b06d7.js +1 -0
- package/dist/ui/_next/static/chunks/f9d4f77d8c130497.js +1 -0
- package/dist/ui/_next/static/chunks/fba113a516eb485f.js +1 -0
- package/dist/ui/_next/static/chunks/fc069e49a569d83c.js +1 -0
- package/dist/ui/_next/static/chunks/fc245016ffebdde3.js +1 -0
- package/dist/ui/_next/static/chunks/fc2d01d74dcf3921.js +1 -0
- package/dist/ui/_next/static/chunks/fe2ad0e00041a87d.js +1 -0
- package/dist/ui/_next/static/chunks/fe571740188dea3a.js +1 -0
- package/dist/ui/_next/static/chunks/turbopack-a5ce95b200d58f7a.js +4 -0
- package/dist/ui/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- package/dist/ui/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- package/dist/ui/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- package/dist/ui/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_AMS-Regular.892f691b.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_AMS-Regular.c30af439.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_AMS-Regular.e6a3cada.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Caligraphic-Bold.2f97783e.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Caligraphic-Bold.a36b8125.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Caligraphic-Bold.db991531.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Caligraphic-Regular.2b13f013.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Caligraphic-Regular.7c50032c.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Caligraphic-Regular.c20eee08.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Fraktur-Bold.5fa2f5e4.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Fraktur-Bold.d514bd28.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Fraktur-Bold.e311399d.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Fraktur-Regular.18139813.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Fraktur-Regular.2a73476d.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Fraktur-Regular.5d62e468.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Main-Bold.6d137c77.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Main-Bold.c24b5ba7.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Main-Bold.f4e2828d.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Main-BoldItalic.079b33ae.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Main-BoldItalic.c003d3ec.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Main-BoldItalic.f1884480.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Main-Italic.26a3619b.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Main-Italic.61da3e93.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Main-Italic.a3746929.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Main-Regular.12644167.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Main-Regular.876b86ad.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Main-Regular.d511f158.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Math-BoldItalic.5c6a6a93.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Math-BoldItalic.ad4dffd5.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Math-BoldItalic.e73b3bf0.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Math-Italic.03974bc4.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Math-Italic.1cd2c488.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Math-Italic.d8564edb.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_SansSerif-Bold.a03f9428.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_SansSerif-Bold.d4608ed5.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_SansSerif-Bold.ff51ef5c.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_SansSerif-Italic.8ed740d6.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_SansSerif-Italic.9245afda.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_SansSerif-Italic.c0b22b1e.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_SansSerif-Regular.128c9bc2.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_SansSerif-Regular.156dbd4e.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_SansSerif-Regular.ee7b0a16.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Script-Regular.00d9a561.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Script-Regular.186c7155.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Script-Regular.afc2fd5a.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Size1-Regular.4450ef36.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Size1-Regular.9aaff96b.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Size1-Regular.e5bf4c74.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Size2-Regular.6a97527b.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Size2-Regular.8fbd32af.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Size2-Regular.f11810ed.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Size3-Regular.45ae8eba.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Size3-Regular.54b7ce9d.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Size3-Regular.9812ade2.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Size4-Regular.44a4d487.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Size4-Regular.927fc5b9.woff2 +0 -0
- package/dist/ui/_next/static/media/KaTeX_Size4-Regular.b211e3d3.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Typewriter-Regular.68b6c3a9.woff +0 -0
- package/dist/ui/_next/static/media/KaTeX_Typewriter-Regular.a1a7ff52.ttf +0 -0
- package/dist/ui/_next/static/media/KaTeX_Typewriter-Regular.ff99d643.woff2 +0 -0
- package/dist/ui/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- package/dist/ui/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
- package/dist/ui/_next/static/media/favicon.0b3bf435.ico +0 -0
- package/dist/ui/_not-found/__next._full.txt +18 -0
- package/dist/ui/_not-found/__next._head.txt +6 -0
- package/dist/ui/_not-found/__next._index.txt +9 -0
- package/dist/ui/_not-found/__next._not-found.__PAGE__.txt +5 -0
- package/dist/ui/_not-found/__next._not-found.txt +4 -0
- package/dist/ui/_not-found/__next._tree.txt +3 -0
- package/dist/ui/_not-found/index.html +1 -0
- package/dist/ui/_not-found/index.txt +18 -0
- package/dist/ui/favicon.ico +0 -0
- package/dist/ui/file.svg +1 -0
- package/dist/ui/globe.svg +1 -0
- package/dist/ui/index.html +1 -0
- package/dist/ui/index.txt +25 -0
- package/dist/ui/next.svg +1 -0
- package/dist/ui/vercel.svg +1 -0
- package/dist/ui/window.svg +1 -0
- package/dist/utils/chat-model.d.ts +11 -0
- package/dist/utils/chat-model.js +37 -0
- package/dist/utils/diff-parser.d.ts +20 -0
- package/dist/utils/diff-parser.js +100 -0
- package/dist/utils/proxy-manager.d.ts +18 -0
- package/dist/utils/proxy-manager.js +42 -0
- package/dist/utils/remote-proxy.d.ts +40 -0
- package/dist/utils/remote-proxy.js +145 -0
- package/dist/utils/worktree-paths.d.ts +13 -0
- package/dist/utils/worktree-paths.js +64 -0
- package/dist/virtual-ws-adapter.d.ts +26 -0
- package/dist/virtual-ws-adapter.js +54 -0
- package/package.json +19 -0
|
@@ -0,0 +1,1425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatSessionManager — lightweight AI chat session manager using Vercel AI SDK.
|
|
3
|
+
*
|
|
4
|
+
* No child processes, no tool tracking, no permission modes.
|
|
5
|
+
* Streams responses from DeepSeek via `streamText` and broadcasts
|
|
6
|
+
* JSON Patches over WebSocket (same architecture as AgentSessionManager).
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
9
|
+
import { streamText, tool, stepCountIs } from "ai";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { resolveChatModel } from "./utils/chat-model.js";
|
|
12
|
+
import WsWebSocket from "ws";
|
|
13
|
+
import { ConversationPatch } from "./conversation-patch.js";
|
|
14
|
+
import { resolveWorktreePath } from "./utils/worktree-paths.js";
|
|
15
|
+
import { proxyToRemote, proxyToRemoteAuto } from "./utils/remote-proxy.js";
|
|
16
|
+
import { VirtualWsAdapter } from "./virtual-ws-adapter.js";
|
|
17
|
+
// ============ Helpers ============
|
|
18
|
+
function stripAnsi(text) {
|
|
19
|
+
return text.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]/g, "");
|
|
20
|
+
}
|
|
21
|
+
function extractLogText(logs, tailLines) {
|
|
22
|
+
const textLogs = logs
|
|
23
|
+
.filter((l) => l.type !== "finished")
|
|
24
|
+
.map((l) => l.data);
|
|
25
|
+
const joined = textLogs.join("");
|
|
26
|
+
const lines = joined.split("\n");
|
|
27
|
+
return stripAnsi(lines.slice(-tailLines).join("\n"));
|
|
28
|
+
}
|
|
29
|
+
// ============ Manager ============
|
|
30
|
+
export class ChatSessionManager {
|
|
31
|
+
/** sessionId → ChatSession */
|
|
32
|
+
sessions = new Map();
|
|
33
|
+
/** projectId:branch → sessionId (one session per project+branch) */
|
|
34
|
+
sessionIndex = new Map();
|
|
35
|
+
/** terminalId → watcher state for active terminal output watchers */
|
|
36
|
+
terminalWatchers = new Map();
|
|
37
|
+
/** sessionId → queued messages waiting to be sent after current stream finishes */
|
|
38
|
+
messageQueue = new Map();
|
|
39
|
+
storage;
|
|
40
|
+
eventBus = null;
|
|
41
|
+
processManager;
|
|
42
|
+
agentSessionManager;
|
|
43
|
+
remoteSessionMap;
|
|
44
|
+
remoteExecutorMap;
|
|
45
|
+
remotePatchCache;
|
|
46
|
+
reverseConnectManager = null;
|
|
47
|
+
browserManager = null;
|
|
48
|
+
/** Pending browser commands waiting for iframe response: commandId → resolve */
|
|
49
|
+
pendingBrowserCommands = new Map();
|
|
50
|
+
constructor(storage, processManager, agentSessionManager, remoteSessionMap, remoteExecutorMap, remotePatchCache, reverseConnectManager, browserManager) {
|
|
51
|
+
this.storage = storage;
|
|
52
|
+
this.processManager = processManager;
|
|
53
|
+
this.agentSessionManager = agentSessionManager;
|
|
54
|
+
this.remoteSessionMap = remoteSessionMap;
|
|
55
|
+
this.remoteExecutorMap = remoteExecutorMap;
|
|
56
|
+
this.remotePatchCache = remotePatchCache;
|
|
57
|
+
this.reverseConnectManager = reverseConnectManager ?? null;
|
|
58
|
+
this.browserManager = browserManager ?? null;
|
|
59
|
+
}
|
|
60
|
+
setEventBus(eventBus) {
|
|
61
|
+
this.eventBus = eventBus;
|
|
62
|
+
this.setupEventListeners();
|
|
63
|
+
}
|
|
64
|
+
setEventListening(sessionId, enabled) {
|
|
65
|
+
const session = this.sessions.get(sessionId);
|
|
66
|
+
if (!session)
|
|
67
|
+
return false;
|
|
68
|
+
session.eventListeningEnabled = enabled;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
getEventListening(sessionId) {
|
|
72
|
+
const session = this.sessions.get(sessionId);
|
|
73
|
+
return session?.eventListeningEnabled ?? false;
|
|
74
|
+
}
|
|
75
|
+
setupEventListeners() {
|
|
76
|
+
if (!this.eventBus)
|
|
77
|
+
return;
|
|
78
|
+
this.eventBus.subscribe((event) => {
|
|
79
|
+
if (event.type === "executor:stopped") {
|
|
80
|
+
this.handleExecutorFinished(event);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
handleExecutorFinished(event) {
|
|
85
|
+
try {
|
|
86
|
+
// Look up executor metadata
|
|
87
|
+
const executor = this.storage.executors.getById(event.executorId);
|
|
88
|
+
if (!executor)
|
|
89
|
+
return;
|
|
90
|
+
// Look up group to get branch
|
|
91
|
+
const group = this.storage.executorGroups.getById(executor.group_id);
|
|
92
|
+
if (!group)
|
|
93
|
+
return;
|
|
94
|
+
const branch = group.branch || null;
|
|
95
|
+
// Find a chat session for this project+branch that has event listening enabled
|
|
96
|
+
const key = `${event.projectId}:${branch ?? ""}`;
|
|
97
|
+
const sessionId = this.sessionIndex.get(key);
|
|
98
|
+
if (!sessionId)
|
|
99
|
+
return;
|
|
100
|
+
const session = this.sessions.get(sessionId);
|
|
101
|
+
if (!session || !session.eventListeningEnabled)
|
|
102
|
+
return;
|
|
103
|
+
// Get tail output from process manager
|
|
104
|
+
const logs = this.processManager.getLogs(event.processId);
|
|
105
|
+
const outputLogs = logs.filter((l) => l.type === "pty" || l.type === "stdout" || l.type === "stderr");
|
|
106
|
+
const tail = outputLogs.slice(-100);
|
|
107
|
+
let raw = tail.map((l) => l.data).join("");
|
|
108
|
+
raw = raw.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
109
|
+
const tailOutput = raw.length > 10000 ? raw.slice(-10000) : raw;
|
|
110
|
+
const exitStatus = event.exitCode === 0 ? "success" : "failed";
|
|
111
|
+
const message = [
|
|
112
|
+
`[Executor Event: Process Finished]`,
|
|
113
|
+
`Executor: "${executor.name}"`,
|
|
114
|
+
`Command: ${executor.command}`,
|
|
115
|
+
`Exit Code: ${event.exitCode} (${exitStatus})`,
|
|
116
|
+
``,
|
|
117
|
+
`Last output:`,
|
|
118
|
+
`---`,
|
|
119
|
+
tailOutput || "(no output captured)",
|
|
120
|
+
`---`,
|
|
121
|
+
``,
|
|
122
|
+
`Summarize in 1-2 sentences.`,
|
|
123
|
+
].join("\n");
|
|
124
|
+
// Send as a user message into the main chat — triggers DeepSeek AI response
|
|
125
|
+
this.enqueueOrSend(sessionId, message);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
console.error(`[ChatSession] handleExecutorFinished error:`, error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
handleBrowserError(sessionId, error) {
|
|
132
|
+
const typeLabels = {
|
|
133
|
+
js_error: "JS Error",
|
|
134
|
+
console_error: "Console Error",
|
|
135
|
+
network_error: "Network Error",
|
|
136
|
+
crash: "Browser Crash",
|
|
137
|
+
};
|
|
138
|
+
const label = typeLabels[error.type] ?? error.type;
|
|
139
|
+
const parts = [
|
|
140
|
+
`[Browser Event: ${label}]`,
|
|
141
|
+
error.url ? `URL: ${error.url}` : null,
|
|
142
|
+
`Error: ${error.message}`,
|
|
143
|
+
error.stack ? `Stack: ${error.stack}` : null,
|
|
144
|
+
``,
|
|
145
|
+
`Summarize in 1-2 sentences.`,
|
|
146
|
+
].filter(Boolean);
|
|
147
|
+
this.enqueueOrSend(sessionId, parts.join("\n"));
|
|
148
|
+
}
|
|
149
|
+
findRemoteSessionForProject(projectId, branch) {
|
|
150
|
+
// Session IDs use format: remote-{serverId}-{projectId}-{remoteSessionId}
|
|
151
|
+
// Match any session that contains the projectId segment
|
|
152
|
+
const projectSegment = `-${projectId}-`;
|
|
153
|
+
let fallback = null;
|
|
154
|
+
for (const [key, info] of this.remoteSessionMap) {
|
|
155
|
+
if (key.startsWith("remote-") && key.includes(projectSegment)) {
|
|
156
|
+
// Exact branch match
|
|
157
|
+
if (info.branch === (branch ?? null)) {
|
|
158
|
+
return { localSessionId: key, info };
|
|
159
|
+
}
|
|
160
|
+
// Keep first match as fallback in case no branch match
|
|
161
|
+
if (!fallback) {
|
|
162
|
+
fallback = { localSessionId: key, info };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (fallback) {
|
|
167
|
+
console.log(`[ChatSession] findRemoteSessionForProject: no exact branch match for branch=${branch ?? "null"}, using fallback session=${fallback.localSessionId} (branch=${fallback.info.branch ?? "null"})`);
|
|
168
|
+
}
|
|
169
|
+
return fallback;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Extract AgentMessage[] from the local remotePatchCache for a given session.
|
|
173
|
+
* Parses cached WS messages and collects ENTRY patch values into an ordered array.
|
|
174
|
+
*/
|
|
175
|
+
extractMessagesFromCache(sessionId) {
|
|
176
|
+
const cacheEntry = this.remotePatchCache.get(sessionId);
|
|
177
|
+
console.log(`[ChatSession] extractMessagesFromCache: sessionId=${sessionId}, cacheExists=${!!cacheEntry}, cachedMsgCount=${cacheEntry?.messages.length ?? 0}, patchCount=${cacheEntry?.patchCount ?? 0}, finished=${cacheEntry?.finished ?? "N/A"}, remoteWsState=${cacheEntry?.remoteWs?.readyState ?? "null"}, subscribers=${cacheEntry?.subscribers.size ?? 0}`);
|
|
178
|
+
if (!cacheEntry || cacheEntry.messages.length === 0)
|
|
179
|
+
return [];
|
|
180
|
+
const result = [];
|
|
181
|
+
// Track patch types for diagnostics
|
|
182
|
+
let entryCount = 0;
|
|
183
|
+
let statusCount = 0;
|
|
184
|
+
let readyCount = 0;
|
|
185
|
+
let finishedCount = 0;
|
|
186
|
+
let otherCount = 0;
|
|
187
|
+
let nonJsonPatchCount = 0;
|
|
188
|
+
let parseErrorCount = 0;
|
|
189
|
+
for (const raw of cacheEntry.messages) {
|
|
190
|
+
try {
|
|
191
|
+
const parsed = JSON.parse(raw);
|
|
192
|
+
if (!parsed.JsonPatch || !Array.isArray(parsed.JsonPatch)) {
|
|
193
|
+
nonJsonPatchCount++;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
for (const op of parsed.JsonPatch) {
|
|
197
|
+
if ((op.op === "add" || op.op === "replace") && op.value?.type === "ENTRY" && op.value.content) {
|
|
198
|
+
const match = op.path?.match(/^\/entries\/(\d+)$/);
|
|
199
|
+
if (match) {
|
|
200
|
+
const index = parseInt(match[1], 10);
|
|
201
|
+
result[index] = op.value.content;
|
|
202
|
+
entryCount++;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else if (op.path === "/status") {
|
|
206
|
+
statusCount++;
|
|
207
|
+
}
|
|
208
|
+
else if (op.value?.type === "READY") {
|
|
209
|
+
readyCount++;
|
|
210
|
+
}
|
|
211
|
+
else if (op.value?.type === "FINISHED") {
|
|
212
|
+
finishedCount++;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
otherCount++;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
parseErrorCount++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const filtered = result.filter(Boolean);
|
|
224
|
+
console.log(`[ChatSession] extractMessagesFromCache: extracted ${filtered.length} messages from ${cacheEntry.messages.length} cached raw messages. Patch breakdown: entry=${entryCount}, status=${statusCount}, ready=${readyCount}, finished=${finishedCount}, other=${otherCount}, nonJsonPatch=${nonJsonPatchCount}, parseErrors=${parseErrorCount}`);
|
|
225
|
+
return filtered;
|
|
226
|
+
}
|
|
227
|
+
summarizeMessages(messages) {
|
|
228
|
+
return messages.map((msg) => {
|
|
229
|
+
switch (msg.type) {
|
|
230
|
+
case "user":
|
|
231
|
+
return { type: "user", content: msg.content };
|
|
232
|
+
case "assistant":
|
|
233
|
+
return { type: "assistant", content: msg.content };
|
|
234
|
+
case "tool_use": {
|
|
235
|
+
const inputStr = typeof msg.input === "string"
|
|
236
|
+
? msg.input
|
|
237
|
+
: JSON.stringify(msg.input);
|
|
238
|
+
return {
|
|
239
|
+
type: "tool_use",
|
|
240
|
+
tool: msg.tool,
|
|
241
|
+
input: inputStr.length > 500 ? inputStr.substring(0, 500) + "..." : inputStr,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
case "tool_result":
|
|
245
|
+
return {
|
|
246
|
+
type: "tool_result",
|
|
247
|
+
tool: msg.tool,
|
|
248
|
+
output: msg.output.length > 500 ? msg.output.substring(0, 500) + "..." : msg.output,
|
|
249
|
+
};
|
|
250
|
+
case "error":
|
|
251
|
+
return { type: "error", message: msg.message };
|
|
252
|
+
case "system":
|
|
253
|
+
return { type: "system", content: msg.content };
|
|
254
|
+
case "thinking":
|
|
255
|
+
return { type: "thinking", content: msg.content };
|
|
256
|
+
default:
|
|
257
|
+
return { type: msg.type };
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
// ---- Terminal watcher ----
|
|
262
|
+
startTerminalWatcher(sessionId, terminalId) {
|
|
263
|
+
// Clean up any existing watcher for this terminal
|
|
264
|
+
this.stopTerminalWatcher(terminalId);
|
|
265
|
+
const DEBOUNCE_MS = 3000;
|
|
266
|
+
const IDLE_TIMEOUT_MS = 60000;
|
|
267
|
+
const MAX_LINES = 100;
|
|
268
|
+
const MAX_BYTES = 8192;
|
|
269
|
+
// Mutable state shared between subscriber callback and flush — kept as a single
|
|
270
|
+
// object so the terminalWatchers map always has a live reference to current timers.
|
|
271
|
+
const state = {
|
|
272
|
+
debounceTimer: null,
|
|
273
|
+
idleTimer: setTimeout(() => this.stopTerminalWatcher(terminalId), IDLE_TIMEOUT_MS),
|
|
274
|
+
outputBuffer: "",
|
|
275
|
+
};
|
|
276
|
+
const flush = () => {
|
|
277
|
+
if (!state.outputBuffer.trim()) {
|
|
278
|
+
console.log(`[ChatSession] terminal watcher flush: empty buffer, skipping (terminal=${terminalId})`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
console.log(`[ChatSession] terminal watcher flush: ${state.outputBuffer.length} bytes (terminal=${terminalId})`);
|
|
282
|
+
// Strip ANSI codes
|
|
283
|
+
let output = state.outputBuffer.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]/g, "");
|
|
284
|
+
// Cap at MAX_LINES / MAX_BYTES
|
|
285
|
+
const lines = output.split("\n");
|
|
286
|
+
if (lines.length > MAX_LINES) {
|
|
287
|
+
output = lines.slice(-MAX_LINES).join("\n");
|
|
288
|
+
}
|
|
289
|
+
if (output.length > MAX_BYTES) {
|
|
290
|
+
output = output.slice(-MAX_BYTES);
|
|
291
|
+
}
|
|
292
|
+
const message = [
|
|
293
|
+
`[Terminal Event: Output]`,
|
|
294
|
+
`Terminal: ${terminalId}`,
|
|
295
|
+
``,
|
|
296
|
+
`Output:`,
|
|
297
|
+
`---`,
|
|
298
|
+
output,
|
|
299
|
+
`---`,
|
|
300
|
+
``,
|
|
301
|
+
`Summarize what happened in 1-2 sentences.`,
|
|
302
|
+
].join("\n");
|
|
303
|
+
// Clean up watcher before sending (prevents duplicate flushes)
|
|
304
|
+
this.stopTerminalWatcher(terminalId);
|
|
305
|
+
// Inject into chat session (queued if a stream is already active)
|
|
306
|
+
this.enqueueOrSend(sessionId, message);
|
|
307
|
+
};
|
|
308
|
+
const unsubscribe = this.processManager.subscribe(terminalId, (msg) => {
|
|
309
|
+
console.log(`[ChatSession] watcher subscriber fired: terminal=${terminalId} type=${msg.type} bufferLen=${state.outputBuffer.length}`);
|
|
310
|
+
if (msg.type === "finished") {
|
|
311
|
+
// Terminal exited — flush what we have
|
|
312
|
+
if (state.debounceTimer)
|
|
313
|
+
clearTimeout(state.debounceTimer);
|
|
314
|
+
state.debounceTimer = null;
|
|
315
|
+
flush();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (msg.type === "pty" || msg.type === "stdout" || msg.type === "stderr") {
|
|
319
|
+
state.outputBuffer += msg.data;
|
|
320
|
+
// Reset debounce timer
|
|
321
|
+
if (state.debounceTimer)
|
|
322
|
+
clearTimeout(state.debounceTimer);
|
|
323
|
+
state.debounceTimer = setTimeout(() => {
|
|
324
|
+
console.log(`[ChatSession] debounce timer fired for terminal=${terminalId}, bufferLen=${state.outputBuffer.length}`);
|
|
325
|
+
flush();
|
|
326
|
+
}, DEBOUNCE_MS);
|
|
327
|
+
// Reset idle timer
|
|
328
|
+
clearTimeout(state.idleTimer);
|
|
329
|
+
state.idleTimer = setTimeout(() => this.stopTerminalWatcher(terminalId), IDLE_TIMEOUT_MS);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
if (!unsubscribe) {
|
|
333
|
+
console.log(`[ChatSession] Cannot watch terminal ${terminalId} — not found in processManager`);
|
|
334
|
+
clearTimeout(state.idleTimer);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
this.terminalWatchers.set(terminalId, {
|
|
338
|
+
unsubscribe,
|
|
339
|
+
state, // live reference — timer IDs stay current
|
|
340
|
+
sessionId,
|
|
341
|
+
});
|
|
342
|
+
console.log(`[ChatSession] Started terminal watcher for terminal=${terminalId} session=${sessionId}`);
|
|
343
|
+
}
|
|
344
|
+
stopTerminalWatcher(terminalId) {
|
|
345
|
+
const watcher = this.terminalWatchers.get(terminalId);
|
|
346
|
+
if (!watcher)
|
|
347
|
+
return;
|
|
348
|
+
watcher.unsubscribe();
|
|
349
|
+
if (watcher.state.debounceTimer)
|
|
350
|
+
clearTimeout(watcher.state.debounceTimer);
|
|
351
|
+
clearTimeout(watcher.state.idleTimer);
|
|
352
|
+
this.terminalWatchers.delete(terminalId);
|
|
353
|
+
console.log(`[ChatSession] Stopped terminal watcher for terminal=${terminalId}`);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Start a watcher for a remote terminal by opening a virtual channel over
|
|
357
|
+
* the existing reverse-connect WebSocket (or a direct WebSocket as fallback).
|
|
358
|
+
* Mirrors the local startTerminalWatcher() — accumulates output with
|
|
359
|
+
* debounce, flushes on "finished" or idle timeout, and feeds the result
|
|
360
|
+
* into enqueueOrSend().
|
|
361
|
+
*/
|
|
362
|
+
startRemoteTerminalWatcher(sessionId, terminalId, remoteInfo) {
|
|
363
|
+
// Clean up any existing watcher for this terminal
|
|
364
|
+
this.stopTerminalWatcher(terminalId);
|
|
365
|
+
const DEBOUNCE_MS = 3000;
|
|
366
|
+
const IDLE_TIMEOUT_MS = 60000;
|
|
367
|
+
const MAX_LINES = 100;
|
|
368
|
+
const MAX_BYTES = 8192;
|
|
369
|
+
const state = {
|
|
370
|
+
debounceTimer: null,
|
|
371
|
+
idleTimer: setTimeout(() => this.stopTerminalWatcher(terminalId), IDLE_TIMEOUT_MS),
|
|
372
|
+
outputBuffer: "",
|
|
373
|
+
};
|
|
374
|
+
const flush = () => {
|
|
375
|
+
const buffered = state.outputBuffer;
|
|
376
|
+
state.outputBuffer = ""; // Clear immediately to prevent double-flush
|
|
377
|
+
if (!buffered.trim()) {
|
|
378
|
+
console.log(`[ChatSession] remote terminal watcher flush: empty buffer, skipping (terminal=${terminalId})`);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
console.log(`[ChatSession] remote terminal watcher flush: ${buffered.length} bytes (terminal=${terminalId})`);
|
|
382
|
+
// Strip ANSI codes
|
|
383
|
+
let output = buffered.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]/g, "");
|
|
384
|
+
const lines = output.split("\n");
|
|
385
|
+
if (lines.length > MAX_LINES) {
|
|
386
|
+
output = lines.slice(-MAX_LINES).join("\n");
|
|
387
|
+
}
|
|
388
|
+
if (output.length > MAX_BYTES) {
|
|
389
|
+
output = output.slice(-MAX_BYTES);
|
|
390
|
+
}
|
|
391
|
+
const message = [
|
|
392
|
+
`[Terminal Event: Output]`,
|
|
393
|
+
`Terminal: ${terminalId}`,
|
|
394
|
+
``,
|
|
395
|
+
`Output:`,
|
|
396
|
+
`---`,
|
|
397
|
+
output,
|
|
398
|
+
`---`,
|
|
399
|
+
``,
|
|
400
|
+
`Summarize what happened in 1-2 sentences.`,
|
|
401
|
+
].join("\n");
|
|
402
|
+
// Clean up watcher before sending (prevents duplicate flushes)
|
|
403
|
+
this.stopTerminalWatcher(terminalId);
|
|
404
|
+
// Inject into chat session
|
|
405
|
+
this.enqueueOrSend(sessionId, message);
|
|
406
|
+
};
|
|
407
|
+
// Open a virtual channel over the existing reverse-connect WebSocket,
|
|
408
|
+
// or fall back to a direct WebSocket connection.
|
|
409
|
+
let remoteWs;
|
|
410
|
+
const useVirtual = this.reverseConnectManager?.isConnected(remoteInfo.remoteServerId);
|
|
411
|
+
if (useVirtual && this.reverseConnectManager) {
|
|
412
|
+
const channelId = randomUUID();
|
|
413
|
+
const wsPath = `/api/executor-processes/${remoteInfo.remoteProcessId}/logs`;
|
|
414
|
+
const wsQuery = `apiKey=${encodeURIComponent(remoteInfo.remoteApiKey)}`;
|
|
415
|
+
const adapter = new VirtualWsAdapter((data) => this.reverseConnectManager.sendChannelData(remoteInfo.remoteServerId, channelId, data), () => this.reverseConnectManager.closeChannel(remoteInfo.remoteServerId, channelId));
|
|
416
|
+
this.reverseConnectManager.setChannelAdapter(remoteInfo.remoteServerId, channelId, adapter);
|
|
417
|
+
this.reverseConnectManager.openVirtualChannel(remoteInfo.remoteServerId, channelId, wsPath, wsQuery);
|
|
418
|
+
remoteWs = adapter;
|
|
419
|
+
console.log(`[ChatSession] Remote terminal watcher: virtual channel opened for ${remoteInfo.remoteProcessId}`);
|
|
420
|
+
setTimeout(() => adapter.emit("open"), 0);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
const cleanRemoteUrl = remoteInfo.remoteUrl.replace(/\/+$/, "");
|
|
424
|
+
const wsProtocol = cleanRemoteUrl.startsWith("https") ? "wss" : "ws";
|
|
425
|
+
const wsUrl = cleanRemoteUrl.replace(/^https?/, wsProtocol);
|
|
426
|
+
const remoteWsUrl = `${wsUrl}/api/executor-processes/${remoteInfo.remoteProcessId}/logs?apiKey=${encodeURIComponent(remoteInfo.remoteApiKey)}`;
|
|
427
|
+
console.log(`[ChatSession] Remote terminal watcher: connecting to ${remoteWsUrl.replace(remoteInfo.remoteApiKey, "***")}`);
|
|
428
|
+
remoteWs = new WsWebSocket(remoteWsUrl);
|
|
429
|
+
}
|
|
430
|
+
const closeWs = () => {
|
|
431
|
+
try {
|
|
432
|
+
remoteWs.close();
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
// already closed
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
remoteWs.on("message", (data) => {
|
|
439
|
+
let msg;
|
|
440
|
+
try {
|
|
441
|
+
msg = JSON.parse(data.toString());
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
// Skip non-log messages (init, error, etc.)
|
|
447
|
+
if (msg.type !== "pty" && msg.type !== "stdout" && msg.type !== "stderr" && msg.type !== "finished")
|
|
448
|
+
return;
|
|
449
|
+
if (msg.type === "finished") {
|
|
450
|
+
if (state.debounceTimer)
|
|
451
|
+
clearTimeout(state.debounceTimer);
|
|
452
|
+
state.debounceTimer = null;
|
|
453
|
+
flush();
|
|
454
|
+
closeWs();
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (msg.type === "pty" || msg.type === "stdout" || msg.type === "stderr") {
|
|
458
|
+
state.outputBuffer += msg.data ?? "";
|
|
459
|
+
// Reset debounce timer
|
|
460
|
+
if (state.debounceTimer)
|
|
461
|
+
clearTimeout(state.debounceTimer);
|
|
462
|
+
state.debounceTimer = setTimeout(() => {
|
|
463
|
+
console.log(`[ChatSession] remote debounce timer fired for terminal=${terminalId}, bufferLen=${state.outputBuffer.length}`);
|
|
464
|
+
flush();
|
|
465
|
+
closeWs();
|
|
466
|
+
}, DEBOUNCE_MS);
|
|
467
|
+
// Reset idle timer
|
|
468
|
+
clearTimeout(state.idleTimer);
|
|
469
|
+
state.idleTimer = setTimeout(() => this.stopTerminalWatcher(terminalId), IDLE_TIMEOUT_MS);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
remoteWs.on("close", () => {
|
|
473
|
+
console.log(`[ChatSession] Remote terminal watcher: connection closed for terminal=${terminalId}`);
|
|
474
|
+
// If we still have buffered output, flush it
|
|
475
|
+
if (state.outputBuffer.trim() && this.terminalWatchers.has(terminalId)) {
|
|
476
|
+
if (state.debounceTimer)
|
|
477
|
+
clearTimeout(state.debounceTimer);
|
|
478
|
+
state.debounceTimer = null;
|
|
479
|
+
flush();
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
remoteWs.on("error", (error) => {
|
|
483
|
+
console.error(`[ChatSession] Remote terminal watcher error for terminal=${terminalId}:`, error);
|
|
484
|
+
});
|
|
485
|
+
const unsubscribe = () => {
|
|
486
|
+
closeWs();
|
|
487
|
+
};
|
|
488
|
+
this.terminalWatchers.set(terminalId, {
|
|
489
|
+
unsubscribe,
|
|
490
|
+
state,
|
|
491
|
+
sessionId,
|
|
492
|
+
});
|
|
493
|
+
console.log(`[ChatSession] Started remote terminal watcher for terminal=${terminalId} session=${sessionId}`);
|
|
494
|
+
}
|
|
495
|
+
// ---- Session lifecycle ----
|
|
496
|
+
getOrCreateSession(projectId, branch) {
|
|
497
|
+
const key = `${projectId}:${branch ?? ""}`;
|
|
498
|
+
const existing = this.sessionIndex.get(key);
|
|
499
|
+
if (existing && this.sessions.has(existing)) {
|
|
500
|
+
return existing;
|
|
501
|
+
}
|
|
502
|
+
const id = randomUUID();
|
|
503
|
+
const session = {
|
|
504
|
+
id,
|
|
505
|
+
projectId,
|
|
506
|
+
branch,
|
|
507
|
+
store: { patches: [], entries: [], nextIndex: 0 },
|
|
508
|
+
subscribers: new Set(),
|
|
509
|
+
status: "stopped",
|
|
510
|
+
abortController: null,
|
|
511
|
+
eventListeningEnabled: false,
|
|
512
|
+
};
|
|
513
|
+
this.sessions.set(id, session);
|
|
514
|
+
this.sessionIndex.set(key, id);
|
|
515
|
+
console.log(`[ChatSession] Created session ${id} for project=${projectId} branch=${branch}`);
|
|
516
|
+
return id;
|
|
517
|
+
}
|
|
518
|
+
getSession(sessionId) {
|
|
519
|
+
return this.sessions.get(sessionId) ?? null;
|
|
520
|
+
}
|
|
521
|
+
getMessages(sessionId) {
|
|
522
|
+
return this.sessions.get(sessionId)?.store.entries ?? [];
|
|
523
|
+
}
|
|
524
|
+
// ---- WebSocket subscription ----
|
|
525
|
+
subscribe(sessionId, ws) {
|
|
526
|
+
const session = this.sessions.get(sessionId);
|
|
527
|
+
if (!session)
|
|
528
|
+
return null;
|
|
529
|
+
// Replay all historical patches
|
|
530
|
+
for (const patch of session.store.patches) {
|
|
531
|
+
const msg = { JsonPatch: patch };
|
|
532
|
+
try {
|
|
533
|
+
ws.send(JSON.stringify(msg));
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
// Client gone
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
// Send current status
|
|
540
|
+
const statusPatch = ConversationPatch.updateStatus(session.status);
|
|
541
|
+
try {
|
|
542
|
+
ws.send(JSON.stringify({ JsonPatch: statusPatch }));
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
// Client gone
|
|
546
|
+
}
|
|
547
|
+
// Signal replay complete
|
|
548
|
+
try {
|
|
549
|
+
ws.send(JSON.stringify({ Ready: true }));
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
// Client gone
|
|
553
|
+
}
|
|
554
|
+
session.subscribers.add(ws);
|
|
555
|
+
return () => {
|
|
556
|
+
session.subscribers.delete(ws);
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
// ---- Tools & system prompt ----
|
|
560
|
+
getSystemPrompt(projectId, branch) {
|
|
561
|
+
return [
|
|
562
|
+
"You are a helpful assistant for a software development workspace.",
|
|
563
|
+
"You can check the status of running executors (dev servers, build processes, etc.) using the getExecutorStatus tool.",
|
|
564
|
+
"You can start executors using the runExecutor tool and stop them using the stopExecutor tool.",
|
|
565
|
+
"When the user asks about running processes, errors, build status, or dev server status, use the getExecutorStatus tool.",
|
|
566
|
+
"When the user asks to start, run, or launch a process, use runExecutor. When they ask to stop or kill a process, use stopExecutor.",
|
|
567
|
+
"You can view the coding agent's conversation history using the getAgentConversation tool.",
|
|
568
|
+
"When the user asks about what the agent is doing, has done, or references agent activities, use this tool.",
|
|
569
|
+
"When you receive an [Executor Event] message, respond in 1-2 sentences only. State what finished, whether it succeeded or failed, and the key detail (e.g. error message) if it failed. Do not repeat the output logs.",
|
|
570
|
+
"You can list active terminal sessions using the listTerminals tool.",
|
|
571
|
+
"You can send commands to a terminal using the runInTerminal tool. The command runs visibly in the user's terminal and returns immediately.",
|
|
572
|
+
"After sending a command, terminal output will arrive as a [Terminal Event] message once the command finishes. Wait for it before commenting on results.",
|
|
573
|
+
"When the user asks to run a command, check something in the terminal, or interact with a shell, use these tools.",
|
|
574
|
+
"If no terminals are open, suggest the user open one in the Terminal tab first.",
|
|
575
|
+
"You can open web pages in the preview browser using the openPreview tool.",
|
|
576
|
+
"You can interact with pages: clickElement, fillInput, selectOption, pressKey.",
|
|
577
|
+
"You can inspect pages: screenshot (returns base64 image), getPageContent (returns text/HTML), waitForElement.",
|
|
578
|
+
"When you receive a [Browser Event] message, respond in 1-2 sentences. State what error occurred and suggest a fix if obvious.",
|
|
579
|
+
`Current workspace: project=${projectId}, branch=${branch ?? "default"}.`,
|
|
580
|
+
].join("\n");
|
|
581
|
+
}
|
|
582
|
+
createTools(projectId, branch, sessionId) {
|
|
583
|
+
const storage = this.storage;
|
|
584
|
+
const processManager = this.processManager;
|
|
585
|
+
const agentSessionManager = this.agentSessionManager;
|
|
586
|
+
const remoteExecutorMap = this.remoteExecutorMap;
|
|
587
|
+
const reverseConnectManager = this.reverseConnectManager;
|
|
588
|
+
const browserManager = this.browserManager;
|
|
589
|
+
const onBrowserError = (error) => {
|
|
590
|
+
if (sessionId)
|
|
591
|
+
this.handleBrowserError(sessionId, error);
|
|
592
|
+
};
|
|
593
|
+
/** Try iframe command first, fall back to Playwright. Returns null if iframe handled it. */
|
|
594
|
+
const tryIframeCommand = sessionId
|
|
595
|
+
? (cmd) => this.sendBrowserCommand(sessionId, cmd)
|
|
596
|
+
: () => Promise.resolve(null);
|
|
597
|
+
return {
|
|
598
|
+
getAgentConversation: tool({
|
|
599
|
+
description: "Get the conversation history of the coding agent in the current workspace. " +
|
|
600
|
+
"Use this when the user asks about what the coding agent is doing, what it has done, " +
|
|
601
|
+
"or needs context about the agent's work. Returns recent messages from the agent session.",
|
|
602
|
+
inputSchema: z.object({
|
|
603
|
+
tailMessages: z
|
|
604
|
+
.number()
|
|
605
|
+
.min(1)
|
|
606
|
+
.max(50)
|
|
607
|
+
.default(20)
|
|
608
|
+
.describe("Number of recent messages to return"),
|
|
609
|
+
}),
|
|
610
|
+
execute: async ({ tailMessages }) => {
|
|
611
|
+
// Collect local session
|
|
612
|
+
let localResult = null;
|
|
613
|
+
let agentSession = agentSessionManager.getSessionByBranch(projectId, branch);
|
|
614
|
+
if (!agentSession) {
|
|
615
|
+
const projectSessions = agentSessionManager.getSessionsByProject(projectId);
|
|
616
|
+
agentSession = projectSessions.find(s => s.status === "running")
|
|
617
|
+
?? projectSessions[0]
|
|
618
|
+
?? null;
|
|
619
|
+
}
|
|
620
|
+
if (agentSession) {
|
|
621
|
+
const allMessages = agentSessionManager.getMessages(agentSession.id);
|
|
622
|
+
const recent = allMessages.slice(-tailMessages);
|
|
623
|
+
localResult = {
|
|
624
|
+
sessionId: agentSession.id,
|
|
625
|
+
status: agentSession.status,
|
|
626
|
+
totalMessages: allMessages.length,
|
|
627
|
+
messages: this.summarizeMessages(recent),
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
// Collect remote session
|
|
631
|
+
let remoteResult = null;
|
|
632
|
+
const remote = this.findRemoteSessionForProject(projectId, branch);
|
|
633
|
+
console.log(`[ChatSession] getAgentConversation: projectId=${projectId}, branch=${branch ?? "null"}, remote=${remote ? remote.localSessionId : "null"}, remoteBranch=${remote?.info.branch ?? "null"}`);
|
|
634
|
+
if (remote) {
|
|
635
|
+
try {
|
|
636
|
+
const result = await proxyToRemote(remote.info.remoteUrl, remote.info.remoteApiKey, "GET", `/api/agent-sessions/${remote.info.remoteSessionId}`);
|
|
637
|
+
console.log(`[ChatSession] getAgentConversation: remote proxy result ok=${result.ok}, status=${result.status}`);
|
|
638
|
+
if (result.ok) {
|
|
639
|
+
const data = result.data;
|
|
640
|
+
let allMessages = data.messages ?? [];
|
|
641
|
+
console.log(`[ChatSession] getAgentConversation: remote returned ${allMessages.length} messages, session.status=${data.session?.status}`);
|
|
642
|
+
// Fallback: if remote returned no messages, extract from local cache
|
|
643
|
+
if (allMessages.length === 0) {
|
|
644
|
+
allMessages = this.extractMessagesFromCache(remote.localSessionId);
|
|
645
|
+
}
|
|
646
|
+
// If session is running but still no messages, poll cache briefly
|
|
647
|
+
// to allow time for ENTRY patches to arrive via WebSocket
|
|
648
|
+
if (allMessages.length === 0 && data.session?.status === "running") {
|
|
649
|
+
const cacheState = this.remotePatchCache.get(remote.localSessionId);
|
|
650
|
+
console.log(`[ChatSession] getAgentConversation: 0 messages for running session, starting retry. Cache state: wsState=${cacheState?.remoteWs?.readyState ?? "null"}, cachedMsgs=${cacheState?.messages.length ?? 0}, patchCount=${cacheState?.patchCount ?? 0}, finished=${cacheState?.finished ?? "N/A"}, reconnecting=${cacheState?.reconnecting ?? "N/A"}`);
|
|
651
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
652
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
653
|
+
allMessages = this.extractMessagesFromCache(remote.localSessionId);
|
|
654
|
+
console.log(`[ChatSession] getAgentConversation: retry attempt ${attempt + 1}/3, extracted ${allMessages.length} messages`);
|
|
655
|
+
if (allMessages.length > 0)
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
658
|
+
if (allMessages.length === 0) {
|
|
659
|
+
const finalCache = this.remotePatchCache.get(remote.localSessionId);
|
|
660
|
+
console.log(`[ChatSession] getAgentConversation: all retries exhausted, still 0 messages. Final cache: wsState=${finalCache?.remoteWs?.readyState ?? "null"}, cachedMsgs=${finalCache?.messages.length ?? 0}, patchCount=${finalCache?.patchCount ?? 0}`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
const recent = allMessages.slice(-tailMessages);
|
|
664
|
+
remoteResult = {
|
|
665
|
+
sessionId: remote.localSessionId,
|
|
666
|
+
status: data.session?.status ?? "unknown",
|
|
667
|
+
totalMessages: allMessages.length,
|
|
668
|
+
messages: this.summarizeMessages(recent),
|
|
669
|
+
...(allMessages.length === 0 && data.session?.status === "running"
|
|
670
|
+
? { note: "Session just started, agent is still initializing. Try again in a few seconds." }
|
|
671
|
+
: {}),
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
console.error(`[ChatSession] getAgentConversation: remote proxy failed status=${result.status}`);
|
|
676
|
+
// Try local cache even if remote returned non-ok status
|
|
677
|
+
const cachedMessages = this.extractMessagesFromCache(remote.localSessionId);
|
|
678
|
+
if (cachedMessages.length > 0) {
|
|
679
|
+
remoteResult = {
|
|
680
|
+
sessionId: remote.localSessionId,
|
|
681
|
+
status: "running",
|
|
682
|
+
totalMessages: cachedMessages.length,
|
|
683
|
+
messages: this.summarizeMessages(cachedMessages.slice(-tailMessages)),
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
catch (err) {
|
|
689
|
+
console.error(`[ChatSession] getAgentConversation: remote proxy error:`, err);
|
|
690
|
+
// Try local cache even if remote is unreachable
|
|
691
|
+
const cachedMessages = this.extractMessagesFromCache(remote.localSessionId);
|
|
692
|
+
if (cachedMessages.length > 0) {
|
|
693
|
+
remoteResult = {
|
|
694
|
+
sessionId: remote.localSessionId,
|
|
695
|
+
status: "running",
|
|
696
|
+
totalMessages: cachedMessages.length,
|
|
697
|
+
messages: this.summarizeMessages(cachedMessages.slice(-tailMessages)),
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (!localResult && !remoteResult) {
|
|
703
|
+
return { local: null, remote: null, message: "No coding agent session found for this workspace." };
|
|
704
|
+
}
|
|
705
|
+
return { local: localResult, remote: remoteResult };
|
|
706
|
+
},
|
|
707
|
+
}),
|
|
708
|
+
getExecutorStatus: tool({
|
|
709
|
+
description: "Get the status of all executors (dev servers, build processes, etc.) in the current workspace. " +
|
|
710
|
+
"Use this when the user asks about running processes, errors, build output, or dev server status.",
|
|
711
|
+
inputSchema: z.object({
|
|
712
|
+
tailLines: z
|
|
713
|
+
.number()
|
|
714
|
+
.min(1)
|
|
715
|
+
.max(100)
|
|
716
|
+
.default(20)
|
|
717
|
+
.describe("Number of recent output lines to include per executor"),
|
|
718
|
+
}),
|
|
719
|
+
execute: async ({ tailLines }) => {
|
|
720
|
+
const group = branch
|
|
721
|
+
? storage.executorGroups.getByBranch(projectId, branch)
|
|
722
|
+
: undefined;
|
|
723
|
+
if (!group) {
|
|
724
|
+
return { executors: [], message: "No executor group found for this workspace." };
|
|
725
|
+
}
|
|
726
|
+
const executors = storage.executors.getByGroupId(group.id);
|
|
727
|
+
const results = executors.map((executor) => {
|
|
728
|
+
const processes = processManager.getProcessesByExecutorId(executor.id);
|
|
729
|
+
const latestProcess = processes[processes.length - 1];
|
|
730
|
+
return {
|
|
731
|
+
name: executor.name,
|
|
732
|
+
command: executor.command,
|
|
733
|
+
isRunning: latestProcess?.isRunning ?? false,
|
|
734
|
+
recentOutput: latestProcess
|
|
735
|
+
? extractLogText(latestProcess.logs, tailLines)
|
|
736
|
+
: "(no process history)",
|
|
737
|
+
};
|
|
738
|
+
});
|
|
739
|
+
return { executors: results };
|
|
740
|
+
},
|
|
741
|
+
}),
|
|
742
|
+
runExecutor: tool({
|
|
743
|
+
description: "Start an executor (dev server, build process, etc.) by name. " +
|
|
744
|
+
"Use this when the user asks to start, run, or launch a process.",
|
|
745
|
+
inputSchema: z.object({
|
|
746
|
+
executorName: z
|
|
747
|
+
.string()
|
|
748
|
+
.describe("Name of the executor to start (case-insensitive match)"),
|
|
749
|
+
}),
|
|
750
|
+
execute: async ({ executorName }) => {
|
|
751
|
+
const group = branch
|
|
752
|
+
? storage.executorGroups.getByBranch(projectId, branch)
|
|
753
|
+
: undefined;
|
|
754
|
+
if (!group) {
|
|
755
|
+
return { success: false, message: "No executor group found for this workspace." };
|
|
756
|
+
}
|
|
757
|
+
const executors = storage.executors.getByGroupId(group.id);
|
|
758
|
+
const executor = executors.find((e) => e.name.toLowerCase() === executorName.toLowerCase());
|
|
759
|
+
if (!executor) {
|
|
760
|
+
const available = executors.map((e) => e.name).join(", ");
|
|
761
|
+
return {
|
|
762
|
+
success: false,
|
|
763
|
+
message: `Executor "${executorName}" not found. Available: ${available || "none"}`,
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
// Check if already running
|
|
767
|
+
const processes = processManager.getProcessesByExecutorId(executor.id);
|
|
768
|
+
const running = processes.find((p) => p.isRunning);
|
|
769
|
+
if (running) {
|
|
770
|
+
return {
|
|
771
|
+
success: false,
|
|
772
|
+
processId: running.processId,
|
|
773
|
+
executorName: executor.name,
|
|
774
|
+
message: `Executor "${executor.name}" is already running (processId=${running.processId}).`,
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
// Resolve project path
|
|
778
|
+
const project = storage.projects.getById(projectId);
|
|
779
|
+
if (!project?.path) {
|
|
780
|
+
return { success: false, message: "No project path configured." };
|
|
781
|
+
}
|
|
782
|
+
const basePath = resolveWorktreePath(project.path, branch);
|
|
783
|
+
try {
|
|
784
|
+
const processId = processManager.start(executor, basePath);
|
|
785
|
+
return {
|
|
786
|
+
success: true,
|
|
787
|
+
processId,
|
|
788
|
+
executorName: executor.name,
|
|
789
|
+
command: executor.command,
|
|
790
|
+
message: `Started "${executor.name}" (${executor.command}).`,
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
catch (err) {
|
|
794
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
795
|
+
return { success: false, message: `Failed to start executor: ${msg}` };
|
|
796
|
+
}
|
|
797
|
+
},
|
|
798
|
+
}),
|
|
799
|
+
stopExecutor: tool({
|
|
800
|
+
description: "Stop a running executor (dev server, build process, etc.) by name. " +
|
|
801
|
+
"Use this when the user asks to stop, kill, or terminate a process.",
|
|
802
|
+
inputSchema: z.object({
|
|
803
|
+
executorName: z
|
|
804
|
+
.string()
|
|
805
|
+
.describe("Name of the executor to stop (case-insensitive match)"),
|
|
806
|
+
}),
|
|
807
|
+
execute: async ({ executorName }) => {
|
|
808
|
+
const group = branch
|
|
809
|
+
? storage.executorGroups.getByBranch(projectId, branch)
|
|
810
|
+
: undefined;
|
|
811
|
+
if (!group) {
|
|
812
|
+
return { success: false, message: "No executor group found for this workspace." };
|
|
813
|
+
}
|
|
814
|
+
const executors = storage.executors.getByGroupId(group.id);
|
|
815
|
+
const executor = executors.find((e) => e.name.toLowerCase() === executorName.toLowerCase());
|
|
816
|
+
if (!executor) {
|
|
817
|
+
const available = executors.map((e) => e.name).join(", ");
|
|
818
|
+
return {
|
|
819
|
+
success: false,
|
|
820
|
+
message: `Executor "${executorName}" not found. Available: ${available || "none"}`,
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
const processes = processManager.getProcessesByExecutorId(executor.id);
|
|
824
|
+
const running = processes.find((p) => p.isRunning);
|
|
825
|
+
if (!running) {
|
|
826
|
+
return {
|
|
827
|
+
success: false,
|
|
828
|
+
executorName: executor.name,
|
|
829
|
+
message: `Executor "${executor.name}" is not running.`,
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
const stopped = processManager.stop(running.processId);
|
|
833
|
+
return {
|
|
834
|
+
success: stopped,
|
|
835
|
+
executorName: executor.name,
|
|
836
|
+
processId: running.processId,
|
|
837
|
+
message: stopped
|
|
838
|
+
? `Stopped "${executor.name}" (processId=${running.processId}).`
|
|
839
|
+
: `Failed to stop "${executor.name}".`,
|
|
840
|
+
};
|
|
841
|
+
},
|
|
842
|
+
}),
|
|
843
|
+
listTerminals: tool({
|
|
844
|
+
description: "List all active terminal sessions in the current workspace. " +
|
|
845
|
+
"Use this to discover available terminals before running commands with runInTerminal.",
|
|
846
|
+
inputSchema: z.object({}),
|
|
847
|
+
execute: async () => {
|
|
848
|
+
// Local terminals
|
|
849
|
+
const localTerminals = processManager.getTerminals(projectId).map((t) => ({
|
|
850
|
+
id: t.id,
|
|
851
|
+
name: t.name,
|
|
852
|
+
cwd: t.cwd,
|
|
853
|
+
branch: t.branch,
|
|
854
|
+
location: "local",
|
|
855
|
+
}));
|
|
856
|
+
// Remote terminals from remoteExecutorMap
|
|
857
|
+
const remoteTerminals = [];
|
|
858
|
+
for (const [key, info] of remoteExecutorMap.entries()) {
|
|
859
|
+
if (!key.startsWith("remote-terminal-"))
|
|
860
|
+
continue;
|
|
861
|
+
if (info.projectId && info.projectId !== projectId)
|
|
862
|
+
continue;
|
|
863
|
+
remoteTerminals.push({
|
|
864
|
+
id: key,
|
|
865
|
+
name: key,
|
|
866
|
+
branch: info.branch,
|
|
867
|
+
location: "remote",
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
const terminals = [...localTerminals, ...remoteTerminals];
|
|
871
|
+
if (terminals.length === 0) {
|
|
872
|
+
return {
|
|
873
|
+
terminals: [],
|
|
874
|
+
message: "No active terminals. The user should open a terminal in the Terminal tab first.",
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
return { terminals };
|
|
878
|
+
},
|
|
879
|
+
}),
|
|
880
|
+
runInTerminal: tool({
|
|
881
|
+
description: "Send a shell command to an active terminal session. The command runs visibly in the user's terminal. " +
|
|
882
|
+
"Returns immediately — terminal output will arrive as a [Terminal Event] message once the command finishes. " +
|
|
883
|
+
"Use listTerminals first to get available terminal IDs. " +
|
|
884
|
+
"Use this when the user asks to run a command, check something, or interact with their shell.",
|
|
885
|
+
inputSchema: z.object({
|
|
886
|
+
terminalId: z.string().describe("ID of the terminal to run the command in (from listTerminals)"),
|
|
887
|
+
command: z.string().describe("The shell command to execute"),
|
|
888
|
+
}),
|
|
889
|
+
execute: async ({ terminalId, command }) => {
|
|
890
|
+
try {
|
|
891
|
+
// Remote terminal — proxy to remote server (fire-and-forget)
|
|
892
|
+
if (terminalId.startsWith("remote-terminal-")) {
|
|
893
|
+
const remoteInfo = remoteExecutorMap.get(terminalId);
|
|
894
|
+
console.log(`[runInTerminal] terminalId=${terminalId}, remoteProcessId=${remoteInfo?.remoteProcessId}, serverId=${remoteInfo?.remoteServerId}`);
|
|
895
|
+
if (!remoteInfo) {
|
|
896
|
+
return { sent: false, message: `Remote terminal ${terminalId} not found.` };
|
|
897
|
+
}
|
|
898
|
+
const result = await proxyToRemoteAuto(remoteInfo.remoteServerId, remoteInfo.remoteUrl, remoteInfo.remoteApiKey, "POST", `/api/path/terminals/${remoteInfo.remoteProcessId}/send`, { command }, { reverseConnectManager: reverseConnectManager ?? undefined });
|
|
899
|
+
if (!result.ok) {
|
|
900
|
+
return { sent: false, message: `Remote send failed: ${JSON.stringify(result.data)}` };
|
|
901
|
+
}
|
|
902
|
+
// Start a remote terminal watcher so output flows back as a [Terminal Event]
|
|
903
|
+
const sessionKey = `${projectId}:${branch ?? ""}`;
|
|
904
|
+
const chatSessionId = this.sessionIndex.get(sessionKey);
|
|
905
|
+
console.log(`[runInTerminal] remote: sessionKey=${sessionKey}, chatSessionId=${chatSessionId ?? "NOT FOUND"}`);
|
|
906
|
+
if (chatSessionId) {
|
|
907
|
+
this.startRemoteTerminalWatcher(chatSessionId, terminalId, remoteInfo);
|
|
908
|
+
}
|
|
909
|
+
else {
|
|
910
|
+
console.log(`[runInTerminal] WARNING: No chat session found — remote terminal watcher NOT started`);
|
|
911
|
+
}
|
|
912
|
+
return { sent: true, message: "Command sent to remote terminal. Output will arrive as a [Terminal Event]." };
|
|
913
|
+
}
|
|
914
|
+
// Local terminal — send command and start watcher
|
|
915
|
+
processManager.sendToTerminal(terminalId, command);
|
|
916
|
+
console.log(`[runInTerminal] Command sent to PTY for terminal=${terminalId}`);
|
|
917
|
+
// Find the chat session that called this tool so we can inject the [Terminal Event] later
|
|
918
|
+
const sessionKey = `${projectId}:${branch ?? ""}`;
|
|
919
|
+
const chatSessionId = this.sessionIndex.get(sessionKey);
|
|
920
|
+
console.log(`[runInTerminal] sessionKey=${sessionKey}, chatSessionId=${chatSessionId ?? "NOT FOUND"}`);
|
|
921
|
+
if (chatSessionId) {
|
|
922
|
+
this.startTerminalWatcher(chatSessionId, terminalId);
|
|
923
|
+
}
|
|
924
|
+
else {
|
|
925
|
+
console.log(`[runInTerminal] WARNING: No chat session found — terminal watcher NOT started`);
|
|
926
|
+
}
|
|
927
|
+
return { sent: true, message: "Command sent to terminal. Output will arrive as a [Terminal Event]." };
|
|
928
|
+
}
|
|
929
|
+
catch (err) {
|
|
930
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
931
|
+
return { sent: false, message: msg };
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
}),
|
|
935
|
+
openPreview: tool({
|
|
936
|
+
description: "Open a URL in the preview browser. " +
|
|
937
|
+
"Use this when the user asks to open, preview, or navigate to a web page. " +
|
|
938
|
+
"This opens the page in the preview iframe (preferred) or falls back to server-side Playwright.",
|
|
939
|
+
inputSchema: z.object({
|
|
940
|
+
url: z.string().describe("The URL to open (e.g. https://remote-server:3000)"),
|
|
941
|
+
}),
|
|
942
|
+
execute: async ({ url }) => {
|
|
943
|
+
// Prefer iframe preview — send WS message to frontend
|
|
944
|
+
if (sessionId) {
|
|
945
|
+
const session = this.sessions.get(sessionId);
|
|
946
|
+
if (session && session.subscribers.size > 0) {
|
|
947
|
+
const raw = JSON.stringify({ openPreviewFrame: { projectId, url } });
|
|
948
|
+
for (const ws of session.subscribers) {
|
|
949
|
+
try {
|
|
950
|
+
ws.send(raw);
|
|
951
|
+
}
|
|
952
|
+
catch { /* ignore */ }
|
|
953
|
+
}
|
|
954
|
+
return { success: true, title: "Preview opened", url };
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
// Fallback to Playwright (no frontend connected)
|
|
958
|
+
if (!browserManager) {
|
|
959
|
+
return { success: false, message: "Browser preview not available." };
|
|
960
|
+
}
|
|
961
|
+
try {
|
|
962
|
+
let session = browserManager.getSession(projectId);
|
|
963
|
+
if (!session) {
|
|
964
|
+
session = await browserManager.startSession(projectId, branch, onBrowserError);
|
|
965
|
+
}
|
|
966
|
+
const result = await browserManager.navigate(projectId, url);
|
|
967
|
+
if (!result) {
|
|
968
|
+
return { success: false, message: "No browser session available." };
|
|
969
|
+
}
|
|
970
|
+
return { success: true, title: result.title, url: result.url };
|
|
971
|
+
}
|
|
972
|
+
catch (err) {
|
|
973
|
+
const msg = err instanceof Error ? err.message : "Failed to open URL";
|
|
974
|
+
return { success: false, error: msg };
|
|
975
|
+
}
|
|
976
|
+
},
|
|
977
|
+
}),
|
|
978
|
+
clickElement: tool({
|
|
979
|
+
description: "Click an element on the page in the preview browser. " +
|
|
980
|
+
"Accepts CSS selectors, text selectors (text=Submit), or role selectors (role=button[name='Submit']).",
|
|
981
|
+
inputSchema: z.object({
|
|
982
|
+
selector: z.string().describe("Selector for the element to click"),
|
|
983
|
+
}),
|
|
984
|
+
execute: async ({ selector }) => {
|
|
985
|
+
// Try iframe first (user sees it live)
|
|
986
|
+
const iframeResult = await tryIframeCommand({ action: "click", selector });
|
|
987
|
+
if (iframeResult)
|
|
988
|
+
return iframeResult;
|
|
989
|
+
// Fallback to Playwright
|
|
990
|
+
const page = browserManager?.getPage(projectId);
|
|
991
|
+
if (!page)
|
|
992
|
+
return { success: false, error: "No browser session. Use openPreview first." };
|
|
993
|
+
try {
|
|
994
|
+
await page.click(selector, { timeout: 5000 });
|
|
995
|
+
return { success: true };
|
|
996
|
+
}
|
|
997
|
+
catch (err) {
|
|
998
|
+
const msg = err instanceof Error ? err.message : "Click failed";
|
|
999
|
+
return { success: false, error: msg };
|
|
1000
|
+
}
|
|
1001
|
+
},
|
|
1002
|
+
}),
|
|
1003
|
+
fillInput: tool({
|
|
1004
|
+
description: "Fill an input or textarea on the page in the preview browser. " +
|
|
1005
|
+
"Clears existing value before typing. Accepts CSS, text, or role selectors.",
|
|
1006
|
+
inputSchema: z.object({
|
|
1007
|
+
selector: z.string().describe("Selector for the input element"),
|
|
1008
|
+
value: z.string().describe("Value to fill"),
|
|
1009
|
+
}),
|
|
1010
|
+
execute: async ({ selector, value }) => {
|
|
1011
|
+
// Try iframe first (user sees it live)
|
|
1012
|
+
const iframeResult = await tryIframeCommand({ action: "fill", selector, value });
|
|
1013
|
+
if (iframeResult)
|
|
1014
|
+
return iframeResult;
|
|
1015
|
+
// Fallback to Playwright
|
|
1016
|
+
const page = browserManager?.getPage(projectId);
|
|
1017
|
+
if (!page)
|
|
1018
|
+
return { success: false, error: "No browser session. Use openPreview first." };
|
|
1019
|
+
try {
|
|
1020
|
+
await page.fill(selector, value, { timeout: 5000 });
|
|
1021
|
+
return { success: true };
|
|
1022
|
+
}
|
|
1023
|
+
catch (err) {
|
|
1024
|
+
const msg = err instanceof Error ? err.message : "Fill failed";
|
|
1025
|
+
return { success: false, error: msg };
|
|
1026
|
+
}
|
|
1027
|
+
},
|
|
1028
|
+
}),
|
|
1029
|
+
selectOption: tool({
|
|
1030
|
+
description: "Select an option from a <select> dropdown in the preview browser.",
|
|
1031
|
+
inputSchema: z.object({
|
|
1032
|
+
selector: z.string().describe("Selector for the <select> element"),
|
|
1033
|
+
value: z.string().describe("Value or label of the option to select"),
|
|
1034
|
+
}),
|
|
1035
|
+
execute: async ({ selector, value }) => {
|
|
1036
|
+
// Try iframe first
|
|
1037
|
+
const iframeResult = await tryIframeCommand({ action: "select", selector, value });
|
|
1038
|
+
if (iframeResult)
|
|
1039
|
+
return iframeResult;
|
|
1040
|
+
// Fallback to Playwright
|
|
1041
|
+
const page = browserManager?.getPage(projectId);
|
|
1042
|
+
if (!page)
|
|
1043
|
+
return { success: false, error: "No browser session. Use openPreview first." };
|
|
1044
|
+
try {
|
|
1045
|
+
await page.selectOption(selector, value, { timeout: 5000 });
|
|
1046
|
+
return { success: true };
|
|
1047
|
+
}
|
|
1048
|
+
catch (err) {
|
|
1049
|
+
const msg = err instanceof Error ? err.message : "Select failed";
|
|
1050
|
+
return { success: false, error: msg };
|
|
1051
|
+
}
|
|
1052
|
+
},
|
|
1053
|
+
}),
|
|
1054
|
+
pressKey: tool({
|
|
1055
|
+
description: "Press a keyboard key in the preview browser (e.g. 'Enter', 'Tab', 'Escape', 'ArrowDown').",
|
|
1056
|
+
inputSchema: z.object({
|
|
1057
|
+
key: z.string().describe("Key to press (e.g. 'Enter', 'Tab', 'Escape')"),
|
|
1058
|
+
}),
|
|
1059
|
+
execute: async ({ key }) => {
|
|
1060
|
+
// Try iframe first
|
|
1061
|
+
const iframeResult = await tryIframeCommand({ action: "pressKey", key });
|
|
1062
|
+
if (iframeResult)
|
|
1063
|
+
return iframeResult;
|
|
1064
|
+
// Fallback to Playwright
|
|
1065
|
+
const page = browserManager?.getPage(projectId);
|
|
1066
|
+
if (!page)
|
|
1067
|
+
return { success: false, error: "No browser session. Use openPreview first." };
|
|
1068
|
+
try {
|
|
1069
|
+
await page.keyboard.press(key);
|
|
1070
|
+
return { success: true };
|
|
1071
|
+
}
|
|
1072
|
+
catch (err) {
|
|
1073
|
+
const msg = err instanceof Error ? err.message : "Key press failed";
|
|
1074
|
+
return { success: false, error: msg };
|
|
1075
|
+
}
|
|
1076
|
+
},
|
|
1077
|
+
}),
|
|
1078
|
+
screenshot: tool({
|
|
1079
|
+
description: "Take a screenshot of the current page in the preview browser. " +
|
|
1080
|
+
"Returns a base64 PNG image. Use this to see the current state of the page.",
|
|
1081
|
+
inputSchema: z.object({}),
|
|
1082
|
+
execute: async () => {
|
|
1083
|
+
const page = browserManager?.getPage(projectId);
|
|
1084
|
+
if (!page)
|
|
1085
|
+
return { success: false, error: "No browser session. Use openPreview first." };
|
|
1086
|
+
try {
|
|
1087
|
+
const buffer = await page.screenshot({ type: "png", fullPage: false });
|
|
1088
|
+
const base64 = buffer.toString("base64");
|
|
1089
|
+
return { success: true, image: base64 };
|
|
1090
|
+
}
|
|
1091
|
+
catch (err) {
|
|
1092
|
+
const msg = err instanceof Error ? err.message : "Screenshot failed";
|
|
1093
|
+
return { success: false, error: msg };
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
1096
|
+
}),
|
|
1097
|
+
getPageContent: tool({
|
|
1098
|
+
description: "Get the text content or HTML of the current page (or a specific element) in the preview browser. " +
|
|
1099
|
+
"If no selector is provided, returns the full page text content.",
|
|
1100
|
+
inputSchema: z.object({
|
|
1101
|
+
selector: z.string().optional().describe("Optional CSS selector to get content of a specific element"),
|
|
1102
|
+
}),
|
|
1103
|
+
execute: async ({ selector }) => {
|
|
1104
|
+
// Try iframe first (reads from the user's actual page)
|
|
1105
|
+
const iframeResult = await tryIframeCommand({ action: "getText", selector: selector ?? undefined });
|
|
1106
|
+
if (iframeResult) {
|
|
1107
|
+
if (iframeResult.success && iframeResult.content) {
|
|
1108
|
+
const capped = iframeResult.content.length > 10000
|
|
1109
|
+
? iframeResult.content.slice(0, 10000) + "\n...(truncated)"
|
|
1110
|
+
: iframeResult.content;
|
|
1111
|
+
return { success: true, content: capped };
|
|
1112
|
+
}
|
|
1113
|
+
return iframeResult;
|
|
1114
|
+
}
|
|
1115
|
+
// Fallback to Playwright
|
|
1116
|
+
const page = browserManager?.getPage(projectId);
|
|
1117
|
+
if (!page)
|
|
1118
|
+
return { success: false, error: "No browser session. Use openPreview first." };
|
|
1119
|
+
try {
|
|
1120
|
+
if (selector) {
|
|
1121
|
+
const text = await page.textContent(selector, { timeout: 5000 });
|
|
1122
|
+
return { success: true, content: text ?? "" };
|
|
1123
|
+
}
|
|
1124
|
+
const text = await page.evaluate(() => document.body.innerText);
|
|
1125
|
+
const capped = text.length > 10000 ? text.slice(0, 10000) + "\n...(truncated)" : text;
|
|
1126
|
+
return { success: true, content: capped };
|
|
1127
|
+
}
|
|
1128
|
+
catch (err) {
|
|
1129
|
+
const msg = err instanceof Error ? err.message : "Failed to get page content";
|
|
1130
|
+
return { success: false, error: msg };
|
|
1131
|
+
}
|
|
1132
|
+
},
|
|
1133
|
+
}),
|
|
1134
|
+
waitForElement: tool({
|
|
1135
|
+
description: "Wait for an element to appear on the page in the preview browser.",
|
|
1136
|
+
inputSchema: z.object({
|
|
1137
|
+
selector: z.string().describe("Selector to wait for"),
|
|
1138
|
+
timeout: z.number().min(1000).max(30000).default(10000).describe("Timeout in milliseconds"),
|
|
1139
|
+
}),
|
|
1140
|
+
execute: async ({ selector, timeout }) => {
|
|
1141
|
+
const page = browserManager?.getPage(projectId);
|
|
1142
|
+
if (!page)
|
|
1143
|
+
return { success: false, error: "No browser session. Use openPreview first." };
|
|
1144
|
+
try {
|
|
1145
|
+
await page.waitForSelector(selector, { timeout });
|
|
1146
|
+
return { success: true };
|
|
1147
|
+
}
|
|
1148
|
+
catch (err) {
|
|
1149
|
+
const msg = err instanceof Error ? err.message : "Wait timed out";
|
|
1150
|
+
return { success: false, error: msg };
|
|
1151
|
+
}
|
|
1152
|
+
},
|
|
1153
|
+
}),
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
// ---- Message queue (prevents concurrent streams on the same session) ----
|
|
1157
|
+
enqueueOrSend(sessionId, content) {
|
|
1158
|
+
const session = this.sessions.get(sessionId);
|
|
1159
|
+
if (!session) {
|
|
1160
|
+
console.log(`[ChatSession] enqueueOrSend: session ${sessionId} not found, dropping message`);
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
if (session.abortController) {
|
|
1164
|
+
// A stream is already active — queue the message
|
|
1165
|
+
let queue = this.messageQueue.get(sessionId);
|
|
1166
|
+
if (!queue) {
|
|
1167
|
+
queue = [];
|
|
1168
|
+
this.messageQueue.set(sessionId, queue);
|
|
1169
|
+
}
|
|
1170
|
+
queue.push(content);
|
|
1171
|
+
console.log(`[ChatSession] Queued message for session ${sessionId} (queue length: ${queue.length})`);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
// No active stream — send immediately
|
|
1175
|
+
console.log(`[ChatSession] enqueueOrSend: sending immediately for session ${sessionId} (abortController=null)`);
|
|
1176
|
+
this.sendMessage(sessionId, content).catch((err) => {
|
|
1177
|
+
console.error(`[ChatSession] enqueueOrSend sendMessage error:`, err);
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
drainQueue(sessionId) {
|
|
1181
|
+
const queue = this.messageQueue.get(sessionId);
|
|
1182
|
+
if (!queue || queue.length === 0) {
|
|
1183
|
+
this.messageQueue.delete(sessionId);
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
const next = queue.shift();
|
|
1187
|
+
if (queue.length === 0)
|
|
1188
|
+
this.messageQueue.delete(sessionId);
|
|
1189
|
+
console.log(`[ChatSession] Draining queued message for session ${sessionId}`);
|
|
1190
|
+
this.sendMessage(sessionId, next).catch((err) => {
|
|
1191
|
+
console.error(`[ChatSession] drainQueue sendMessage error:`, err);
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
// ---- Send message & stream AI response ----
|
|
1195
|
+
async sendMessage(sessionId, content) {
|
|
1196
|
+
const session = this.sessions.get(sessionId);
|
|
1197
|
+
if (!session) {
|
|
1198
|
+
console.log(`[ChatSession] sendMessage: session ${sessionId} not found`);
|
|
1199
|
+
return false;
|
|
1200
|
+
}
|
|
1201
|
+
console.log(`[ChatSession] sendMessage called: session=${sessionId}, contentLen=${content.length}, isTerminalEvent=${content.includes("[Terminal Event]")}`);
|
|
1202
|
+
// 1. Push user message
|
|
1203
|
+
const userMsg = { type: "user", content, timestamp: Date.now() };
|
|
1204
|
+
this.pushEntry(session, userMsg);
|
|
1205
|
+
// 2. Update status to running
|
|
1206
|
+
session.status = "running";
|
|
1207
|
+
this.broadcastPatch(session, ConversationPatch.updateStatus("running"));
|
|
1208
|
+
// 3. Build messages array for AI SDK
|
|
1209
|
+
const messages = session.store.entries
|
|
1210
|
+
.filter((e) => e.type === "user" || e.type === "assistant")
|
|
1211
|
+
.map((e) => ({
|
|
1212
|
+
role: e.type,
|
|
1213
|
+
content: typeof e.content === "string" ? e.content : e.content.filter(p => p.type === "text").map(p => p.text).join("\n"),
|
|
1214
|
+
}));
|
|
1215
|
+
// 4. Stream response
|
|
1216
|
+
const abortController = new AbortController();
|
|
1217
|
+
session.abortController = abortController;
|
|
1218
|
+
let assistantIndex = null;
|
|
1219
|
+
let accumulatedText = "";
|
|
1220
|
+
try {
|
|
1221
|
+
const result = streamText({
|
|
1222
|
+
model: resolveChatModel(this.storage),
|
|
1223
|
+
system: this.getSystemPrompt(session.projectId, session.branch),
|
|
1224
|
+
messages,
|
|
1225
|
+
tools: this.createTools(session.projectId, session.branch, session.id),
|
|
1226
|
+
stopWhen: stepCountIs(3),
|
|
1227
|
+
abortSignal: abortController.signal,
|
|
1228
|
+
});
|
|
1229
|
+
for await (const part of result.fullStream) {
|
|
1230
|
+
if (abortController.signal.aborted)
|
|
1231
|
+
break;
|
|
1232
|
+
switch (part.type) {
|
|
1233
|
+
case "text-delta": {
|
|
1234
|
+
accumulatedText += part.text;
|
|
1235
|
+
if (assistantIndex === null) {
|
|
1236
|
+
// First chunk — create the assistant entry
|
|
1237
|
+
const assistantMsg = {
|
|
1238
|
+
type: "assistant",
|
|
1239
|
+
content: accumulatedText,
|
|
1240
|
+
partial: true,
|
|
1241
|
+
timestamp: Date.now(),
|
|
1242
|
+
};
|
|
1243
|
+
assistantIndex = session.store.nextIndex;
|
|
1244
|
+
session.store.nextIndex++;
|
|
1245
|
+
const patch = ConversationPatch.addEntry(assistantIndex, assistantMsg);
|
|
1246
|
+
session.store.patches.push(patch);
|
|
1247
|
+
session.store.entries[assistantIndex] = assistantMsg;
|
|
1248
|
+
this.broadcastPatch(session, patch);
|
|
1249
|
+
}
|
|
1250
|
+
else {
|
|
1251
|
+
// Subsequent chunks — replace entry
|
|
1252
|
+
const assistantMsg = {
|
|
1253
|
+
type: "assistant",
|
|
1254
|
+
content: accumulatedText,
|
|
1255
|
+
partial: true,
|
|
1256
|
+
timestamp: Date.now(),
|
|
1257
|
+
};
|
|
1258
|
+
const patch = ConversationPatch.replaceEntry(assistantIndex, assistantMsg);
|
|
1259
|
+
session.store.patches.push(patch);
|
|
1260
|
+
session.store.entries[assistantIndex] = assistantMsg;
|
|
1261
|
+
this.broadcastPatch(session, patch);
|
|
1262
|
+
}
|
|
1263
|
+
break;
|
|
1264
|
+
}
|
|
1265
|
+
case "tool-call": {
|
|
1266
|
+
// Finalize any partial assistant message before the tool call
|
|
1267
|
+
if (assistantIndex !== null && accumulatedText) {
|
|
1268
|
+
const finalMsg = {
|
|
1269
|
+
type: "assistant",
|
|
1270
|
+
content: accumulatedText,
|
|
1271
|
+
partial: false,
|
|
1272
|
+
timestamp: Date.now(),
|
|
1273
|
+
};
|
|
1274
|
+
const patch = ConversationPatch.replaceEntry(assistantIndex, finalMsg);
|
|
1275
|
+
session.store.patches.push(patch);
|
|
1276
|
+
session.store.entries[assistantIndex] = finalMsg;
|
|
1277
|
+
this.broadcastPatch(session, patch);
|
|
1278
|
+
}
|
|
1279
|
+
const toolUseMsg = {
|
|
1280
|
+
type: "tool_use",
|
|
1281
|
+
tool: part.toolName,
|
|
1282
|
+
input: part.input,
|
|
1283
|
+
toolUseId: part.toolCallId,
|
|
1284
|
+
timestamp: Date.now(),
|
|
1285
|
+
};
|
|
1286
|
+
this.pushEntry(session, toolUseMsg);
|
|
1287
|
+
// Reset so next text starts a new assistant message
|
|
1288
|
+
assistantIndex = null;
|
|
1289
|
+
accumulatedText = "";
|
|
1290
|
+
break;
|
|
1291
|
+
}
|
|
1292
|
+
case "tool-result": {
|
|
1293
|
+
const output = part.output;
|
|
1294
|
+
const toolResultMsg = {
|
|
1295
|
+
type: "tool_result",
|
|
1296
|
+
tool: part.toolName,
|
|
1297
|
+
output: typeof output === "string" ? output : JSON.stringify(output),
|
|
1298
|
+
toolUseId: part.toolCallId,
|
|
1299
|
+
timestamp: Date.now(),
|
|
1300
|
+
};
|
|
1301
|
+
this.pushEntry(session, toolResultMsg);
|
|
1302
|
+
break;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
// 5. Finalize — mark as non-partial
|
|
1307
|
+
if (assistantIndex !== null) {
|
|
1308
|
+
const finalMsg = {
|
|
1309
|
+
type: "assistant",
|
|
1310
|
+
content: accumulatedText,
|
|
1311
|
+
partial: false,
|
|
1312
|
+
timestamp: Date.now(),
|
|
1313
|
+
};
|
|
1314
|
+
const patch = ConversationPatch.replaceEntry(assistantIndex, finalMsg);
|
|
1315
|
+
session.store.patches.push(patch);
|
|
1316
|
+
session.store.entries[assistantIndex] = finalMsg;
|
|
1317
|
+
this.broadcastPatch(session, patch);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
catch (err) {
|
|
1321
|
+
// Don't push error for intentional abort
|
|
1322
|
+
if (abortController.signal.aborted) {
|
|
1323
|
+
// Finalize partial message if we have one
|
|
1324
|
+
if (assistantIndex !== null && accumulatedText) {
|
|
1325
|
+
const finalMsg = {
|
|
1326
|
+
type: "assistant",
|
|
1327
|
+
content: accumulatedText,
|
|
1328
|
+
partial: false,
|
|
1329
|
+
timestamp: Date.now(),
|
|
1330
|
+
};
|
|
1331
|
+
const patch = ConversationPatch.replaceEntry(assistantIndex, finalMsg);
|
|
1332
|
+
session.store.patches.push(patch);
|
|
1333
|
+
session.store.entries[assistantIndex] = finalMsg;
|
|
1334
|
+
this.broadcastPatch(session, patch);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
else {
|
|
1338
|
+
const errorMessage = err instanceof Error ? err.message : "Unknown error";
|
|
1339
|
+
console.error(`[ChatSession] Stream error for ${sessionId}:`, errorMessage);
|
|
1340
|
+
const errorMsg = {
|
|
1341
|
+
type: "error",
|
|
1342
|
+
message: errorMessage,
|
|
1343
|
+
timestamp: Date.now(),
|
|
1344
|
+
};
|
|
1345
|
+
this.pushEntry(session, errorMsg);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
finally {
|
|
1349
|
+
session.abortController = null;
|
|
1350
|
+
session.status = "stopped";
|
|
1351
|
+
this.broadcastPatch(session, ConversationPatch.updateStatus("stopped"));
|
|
1352
|
+
// Process any queued messages (e.g. [Terminal Event] that arrived during this stream)
|
|
1353
|
+
this.drainQueue(sessionId);
|
|
1354
|
+
}
|
|
1355
|
+
return true;
|
|
1356
|
+
}
|
|
1357
|
+
// ---- Stop generation ----
|
|
1358
|
+
stopGeneration(sessionId) {
|
|
1359
|
+
const session = this.sessions.get(sessionId);
|
|
1360
|
+
if (!session || !session.abortController)
|
|
1361
|
+
return false;
|
|
1362
|
+
session.abortController.abort();
|
|
1363
|
+
return true;
|
|
1364
|
+
}
|
|
1365
|
+
// ---- Internal helpers ----
|
|
1366
|
+
pushEntry(session, entry) {
|
|
1367
|
+
const index = session.store.nextIndex;
|
|
1368
|
+
session.store.nextIndex++;
|
|
1369
|
+
const patch = ConversationPatch.addEntry(index, entry);
|
|
1370
|
+
session.store.patches.push(patch);
|
|
1371
|
+
session.store.entries[index] = entry;
|
|
1372
|
+
this.broadcastPatch(session, patch);
|
|
1373
|
+
}
|
|
1374
|
+
broadcastPatch(session, patch) {
|
|
1375
|
+
const raw = JSON.stringify({ JsonPatch: patch });
|
|
1376
|
+
for (const ws of session.subscribers) {
|
|
1377
|
+
try {
|
|
1378
|
+
ws.send(raw);
|
|
1379
|
+
}
|
|
1380
|
+
catch {
|
|
1381
|
+
// Client gone, will be cleaned up on close
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
// ---- Browser command via iframe ----
|
|
1386
|
+
/**
|
|
1387
|
+
* Send a browser command to the frontend via WebSocket.
|
|
1388
|
+
* The frontend forwards it to the iframe's injected script via postMessage.
|
|
1389
|
+
* Returns the result or null if no subscribers or timeout.
|
|
1390
|
+
*/
|
|
1391
|
+
sendBrowserCommand(sessionId, command, timeoutMs = 5000) {
|
|
1392
|
+
const session = this.sessions.get(sessionId);
|
|
1393
|
+
if (!session || session.subscribers.size === 0) {
|
|
1394
|
+
return Promise.resolve(null); // No frontend connected
|
|
1395
|
+
}
|
|
1396
|
+
const id = `bcmd-${randomUUID()}`;
|
|
1397
|
+
const fullCommand = { id, ...command };
|
|
1398
|
+
return new Promise((resolve) => {
|
|
1399
|
+
const timer = setTimeout(() => {
|
|
1400
|
+
this.pendingBrowserCommands.delete(id);
|
|
1401
|
+
resolve(null); // Timeout — frontend didn't respond
|
|
1402
|
+
}, timeoutMs);
|
|
1403
|
+
this.pendingBrowserCommands.set(id, { resolve, timer });
|
|
1404
|
+
// Broadcast to all subscribers
|
|
1405
|
+
const raw = JSON.stringify({ browserCommand: fullCommand });
|
|
1406
|
+
for (const ws of session.subscribers) {
|
|
1407
|
+
try {
|
|
1408
|
+
ws.send(raw);
|
|
1409
|
+
}
|
|
1410
|
+
catch { /* client gone */ }
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Called when the frontend sends a browserResult message back over WebSocket.
|
|
1416
|
+
*/
|
|
1417
|
+
handleBrowserResult(result) {
|
|
1418
|
+
const pending = this.pendingBrowserCommands.get(result.id);
|
|
1419
|
+
if (!pending)
|
|
1420
|
+
return; // Already timed out or duplicate
|
|
1421
|
+
clearTimeout(pending.timer);
|
|
1422
|
+
this.pendingBrowserCommands.delete(result.id);
|
|
1423
|
+
pending.resolve(result);
|
|
1424
|
+
}
|
|
1425
|
+
}
|