wind-agent-cli 1.37.2 → 1.37.3
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/LICENSE +718 -718
- package/README.md +1 -1
- package/dist/api-docs.html +901 -901
- package/dist/assets/{CodeEditor-B2Wf7Fg0.js → CodeEditor-BqmxpZ_D.js} +1 -1
- package/dist/assets/{FileTree-N2IkkHlv.js → FileTree-DLE7Ioai.js} +1 -1
- package/dist/assets/{GitPanel-DZCeuFZ2.js → GitPanel-f8RMRa8D.js} +1 -1
- package/dist/assets/{MarkdownPreview-C0tqYnVu.js → MarkdownPreview-93h_1tJa.js} +1 -1
- package/dist/assets/{PluginTabContent-Bnuf-fb8.js → PluginTabContent-DU1jjkXP.js} +1 -1
- package/dist/assets/{Shell-BfFnMZ-f.js → Shell-CN_2MRIM.js} +1 -1
- package/dist/assets/{StandaloneShell-QmyYwNAl.js → StandaloneShell-D0avS8UE.js} +1 -1
- package/dist/assets/{TaskMasterPanel-fMsmvN9L.js → TaskMasterPanel-Cpf1Q5Ph.js} +3 -3
- package/dist/assets/{abnfDiagram-N423BO3Z-DjPU7HLs.js → abnfDiagram-N423BO3Z-p3aJIm4T.js} +1 -1
- package/dist/assets/{arc-Pdq6Tvr3.js → arc-CTeud6Fl.js} +1 -1
- package/dist/assets/{architectureDiagram-T3A2C74G-D0JnT0Vh.js → architectureDiagram-T3A2C74G-CG7kRCJU.js} +1 -1
- package/dist/assets/{blockDiagram-VBNYF7ZC-DpaEf3mv.js → blockDiagram-VBNYF7ZC-oSjQ1SgE.js} +1 -1
- package/dist/assets/{c4Diagram-5PPSVZJV-MpqlPGFi.js → c4Diagram-5PPSVZJV-RkzF0DJV.js} +1 -1
- package/dist/assets/channel-BfQbarn9.js +1 -0
- package/dist/assets/{chunk-2GRJ4B5K-DoSdNo8u.js → chunk-2GRJ4B5K-B07uZ07O.js} +1 -1
- package/dist/assets/{chunk-2Q5K7J3B-CE7I3uMn.js → chunk-2Q5K7J3B-DNSfO7xS.js} +1 -1
- package/dist/assets/{chunk-5RXB4S5H-B4UQ8snD.js → chunk-5RXB4S5H-BTeYcgyb.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-B55XgqHZ.js → chunk-5VM5RSS4-B7vZV0zC.js} +1 -1
- package/dist/assets/{chunk-6Q2QTUOP-BS1KpefW.js → chunk-6Q2QTUOP-DuS_xUGG.js} +1 -1
- package/dist/assets/{chunk-GF5L2VYU-DkEPedJP.js → chunk-GF5L2VYU-CKLZ3iVD.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DOtkNhMm.js → chunk-JWPE2WC7-DD2hcXvs.js} +1 -1
- package/dist/assets/{chunk-KBJHAD2P-BXLjviqM.js → chunk-KBJHAD2P-Cz-l38qp.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-C50fjNNz.js → chunk-RYQCIY6F-DGFIU1YM.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-QBYqCkb7.js → chunk-XXDRQBXY-CmfzoYFl.js} +1 -1
- package/dist/assets/classDiagram-JCYQIIEL-Bpfe3kq7.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-Bpfe3kq7.js +1 -0
- package/dist/assets/{code-xml-VK1YfShP.js → code-xml-CLpHzEOP.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-DrVTo1Nh.js → cose-bilkent-JH36ORCC-DlFcLXCB.js} +1 -1
- package/dist/assets/{cynefin-VYW2F7L2-DnJa_OlQ.js → cynefin-VYW2F7L2-P1HecReO.js} +1 -1
- package/dist/assets/{cynefinDiagram-MW4NZA55-CCxBjw9E.js → cynefinDiagram-MW4NZA55-COgduz5P.js} +1 -1
- package/dist/assets/{dagre-VZM6K2ZE-C_GvE4lu.js → dagre-VZM6K2ZE-Bbb-siGp.js} +1 -1
- package/dist/assets/{diagram-7IWD3JNH-pFzs9HEu.js → diagram-7IWD3JNH-9CWGFMYR.js} +1 -1
- package/dist/assets/{diagram-B4RE2ZJO-CcehQC7x.js → diagram-B4RE2ZJO-TWK59T6V.js} +1 -1
- package/dist/assets/{diagram-LBJQPF4R-hfKcrrxe.js → diagram-LBJQPF4R-ErMC9yaa.js} +1 -1
- package/dist/assets/{diagram-Q27KOJAE-C1semJfK.js → diagram-Q27KOJAE-Dt_-C7hV.js} +1 -1
- package/dist/assets/{diagram-UB23O5K3-57hvXQXO.js → diagram-UB23O5K3-BJ1DnRAF.js} +1 -1
- package/dist/assets/{ebnfDiagram-BXEA7PRR-9KIAlAVw.js → ebnfDiagram-BXEA7PRR-CE1YaQ9s.js} +1 -1
- package/dist/assets/{erDiagram-JOGREHBK-B7OzJ767.js → erDiagram-JOGREHBK-Bqk7N9aY.js} +1 -1
- package/dist/assets/{flowDiagram-UKHOOZJN-CJV8fQPR.js → flowDiagram-UKHOOZJN-DEr2II5V.js} +1 -1
- package/dist/assets/{ganttDiagram-PKOTCBZU-CbMNWWQu.js → ganttDiagram-PKOTCBZU-CWYAUeYN.js} +1 -1
- package/dist/assets/{gitGraphDiagram-DS77QQ5N-I_vZ5waO.js → gitGraphDiagram-DS77QQ5N-tdEcEIM6.js} +1 -1
- package/dist/assets/{index-ZHwIojzi.js → index-B-sTfSZq.js} +1 -1
- package/dist/assets/{index-GFhXZyl2.js → index-BSe3UiFA.js} +1 -1
- package/dist/assets/{index-DFnVoqpx.js → index-B_FDMWar.js} +5 -5
- package/dist/assets/{index-DlSeAkw_.js → index-CWJtg2pR.js} +1 -1
- package/dist/assets/{infoDiagram-6WML65LV-Dh20Tsbq.js → infoDiagram-6WML65LV-A3IqRUnO.js} +1 -1
- package/dist/assets/{ishikawaDiagram-WSZJBQD7-DH2Sm3ko.js → ishikawaDiagram-WSZJBQD7-B2uL6bkK.js} +1 -1
- package/dist/assets/{journeyDiagram-NVQOT4AX-BNFQqlVu.js → journeyDiagram-NVQOT4AX-D9JB-hRW.js} +1 -1
- package/dist/assets/{kanban-definition-27J2QSJJ-B3iL0ztJ.js → kanban-definition-27J2QSJJ-DmlgiV7K.js} +1 -1
- package/dist/assets/{linear-Cwe9dEKA.js → linear-Ct_eoJQk.js} +1 -1
- package/dist/assets/{mermaid.core-BYRQSshi.js → mermaid.core-CQ698HNn.js} +5 -5
- package/dist/assets/{mindmap-definition-FAOFIHXS-BgfBYMzr.js → mindmap-definition-FAOFIHXS-DGtMgQhf.js} +1 -1
- package/dist/assets/{pegDiagram-VL7TDLO6-CNYpECr8.js → pegDiagram-VL7TDLO6-CO6vP4n9.js} +1 -1
- package/dist/assets/{pieDiagram-7S7Q4E2Y-DeSrxc4I.js → pieDiagram-7S7Q4E2Y-DMkt_Zfr.js} +1 -1
- package/dist/assets/{quadrantDiagram-CIZ2JOQS-BXVVQsa6.js → quadrantDiagram-CIZ2JOQS-DgNxRz6t.js} +1 -1
- package/dist/assets/{railroadDiagram-AXF67PYL-JTroIW8F.js → railroadDiagram-AXF67PYL-DKz9UqA9.js} +1 -1
- package/dist/assets/{requirementDiagram-LRYGKXZP-C1Z_inLz.js → requirementDiagram-LRYGKXZP-L84XyGgV.js} +1 -1
- package/dist/assets/{sankeyDiagram-W5VNT64P-DCJgKFUk.js → sankeyDiagram-W5VNT64P-CQqWbJ3j.js} +1 -1
- package/dist/assets/{sequenceDiagram-SI44F4Z6-D8Wrx5Vc.js → sequenceDiagram-SI44F4Z6--tnzS2tb.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-CXqD68vD.js → sizeCapture-X5ZJPWSS-DujwEOtm.js} +1 -1
- package/dist/assets/{stateDiagram-OKZ733FA-CF0Ekqjo.js → stateDiagram-OKZ733FA-BK3TcZY2.js} +1 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-_medyomK.js +1 -0
- package/dist/assets/{swimlanes-SLNWSIFB-DVIax_k4.js → swimlanes-SLNWSIFB-naX-Uv1B.js} +2 -2
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-DstIv3TC.js +8 -0
- package/dist/assets/{timeline-definition-Z64GVDOM-DSqk3ApG.js → timeline-definition-Z64GVDOM-C-vP_ddU.js} +1 -1
- package/dist/assets/{vennDiagram-T6HMQDX7-DNbTqyG5.js → vennDiagram-T6HMQDX7-DN-I7o-7.js} +1 -1
- package/dist/assets/{wardleyDiagram-T6FBY63Y-BNCpsFZV.js → wardleyDiagram-T6FBY63Y-Db9dsaXv.js} +1 -1
- package/dist/assets/{xychartDiagram-ELKLHX3M-XsVZphHr.js → xychartDiagram-ELKLHX3M-DE6OxW_p.js} +1 -1
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/favicon.svg +8 -8
- package/dist/generate-icons.js +48 -48
- package/dist/icons/codex-white.svg +3 -3
- package/dist/icons/codex.svg +3 -3
- package/dist/icons/cursor-white.svg +11 -11
- package/dist/icons/icon-128x128.svg +11 -11
- package/dist/icons/icon-144x144.svg +11 -11
- package/dist/icons/icon-152x152.svg +11 -11
- package/dist/icons/icon-192x192.svg +11 -11
- package/dist/icons/icon-384x384.svg +11 -11
- package/dist/icons/icon-512x512.svg +11 -11
- package/dist/icons/icon-72x72.svg +11 -11
- package/dist/icons/icon-96x96.svg +11 -11
- package/dist/icons/icon-template.svg +11 -11
- package/dist/index.html +56 -56
- package/dist/logo.svg +17 -17
- package/dist/manifest.json +60 -60
- package/dist/sw.js +124 -124
- package/dist-server/server/modules/cli/cli.service.js +36 -36
- package/dist-server/server/modules/cli/sandbox.service.js +38 -38
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +74 -74
- package/dist-server/server/modules/commands/commands.routes.js +24 -24
- package/dist-server/server/modules/database/migrations.js +190 -190
- package/dist-server/server/modules/database/repositories/api-keys.js +3 -3
- package/dist-server/server/modules/database/repositories/github-tokens.js +2 -2
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +28 -28
- package/dist-server/server/modules/database/repositories/notification-preferences.js +4 -4
- package/dist-server/server/modules/database/repositories/projects.db.js +61 -61
- package/dist-server/server/modules/database/repositories/provider-models.js +35 -35
- package/dist-server/server/modules/database/repositories/push-subscriptions.js +5 -5
- package/dist-server/server/modules/database/repositories/scan-state.db.js +5 -5
- package/dist-server/server/modules/database/repositories/sessions.db.js +92 -92
- package/dist-server/server/modules/database/schema.js +177 -177
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +6 -6
- package/dist-server/server/modules/git/git.routes.js +17 -17
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +12 -12
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +9 -9
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +17 -17
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +25 -25
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +27 -27
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js +9 -9
- package/dist-server/server/modules/providers/tests/mcp.test.js +3 -3
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +96 -96
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +18 -18
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js +419 -419
- package/electron/cloud.js +260 -260
- package/electron/desktopNotifications.js +378 -378
- package/electron/desktopWindow.js +766 -766
- package/electron/launcher/index.html +14 -14
- package/electron/launcher/launcher.css +801 -801
- package/electron/launcher/launcher.js +687 -687
- package/electron/localServer.js +549 -549
- package/electron/main.js +944 -944
- package/electron/preload.cjs +60 -60
- package/electron/scripts/generate-macos-icon.js +62 -62
- package/electron/serverInstaller.js +277 -277
- package/electron/tabs.js +87 -87
- package/electron/viewHost.js +331 -331
- package/package.json +250 -247
- package/public/api-docs.html +901 -901
- package/scripts/fix-node-pty.js +67 -67
- package/scripts/promote-dist-server.mjs +50 -50
- package/scripts/release/build-server-bundle.js +176 -176
- package/scripts/release/prepare-desktop-app.js +152 -152
- package/server/index.ts +399 -399
- package/server/load-env.ts +47 -47
- package/server/modules/agent/agent.module.ts +52 -52
- package/server/modules/agent/agent.routes.ts +1287 -1287
- package/server/modules/agent/index.ts +2 -2
- package/server/modules/agent/tests/agent.routes.test.ts +205 -205
- package/server/modules/assets/assets.routes.ts +150 -150
- package/server/modules/assets/index.ts +3 -3
- package/server/modules/assets/services/image-assets.service.ts +127 -127
- package/server/modules/assets/tests/image-assets.service.test.ts +74 -74
- package/server/modules/auth/auth.middleware.ts +163 -163
- package/server/modules/auth/auth.module.ts +38 -38
- package/server/modules/auth/auth.routes.ts +57 -57
- package/server/modules/auth/auth.service.ts +155 -155
- package/server/modules/auth/index.ts +9 -9
- package/server/modules/auth/tests/auth.service.test.ts +95 -95
- package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -120
- package/server/modules/browser-use/browser-use-mcp.ts +386 -386
- package/server/modules/browser-use/browser-use-runtime.ts +10 -10
- package/server/modules/browser-use/browser-use.routes.ts +96 -96
- package/server/modules/browser-use/browser-use.service.ts +836 -836
- package/server/modules/browser-use/index.ts +11 -11
- package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -10
- package/server/modules/cli/cli.module.ts +93 -93
- package/server/modules/cli/cli.service.ts +248 -248
- package/server/modules/cli/cli.ts +19 -19
- package/server/modules/cli/index.ts +2 -2
- package/server/modules/cli/sandbox.service.ts +398 -398
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -186
- package/server/modules/cli/tests/cli.service.test.ts +86 -86
- package/server/modules/cli/tests/sandbox.service.test.ts +58 -58
- package/server/modules/commands/commands.module.ts +22 -22
- package/server/modules/commands/commands.routes.ts +586 -586
- package/server/modules/commands/index.ts +2 -2
- package/server/modules/commands/tests/commands.test.ts +109 -109
- package/server/modules/database/connection.ts +143 -143
- package/server/modules/database/index.ts +17 -17
- package/server/modules/database/init-db.ts +17 -17
- package/server/modules/database/migrations.ts +515 -515
- package/server/modules/database/repositories/api-keys.ts +119 -119
- package/server/modules/database/repositories/app-config.ts +53 -53
- package/server/modules/database/repositories/credentials.ts +106 -106
- package/server/modules/database/repositories/github-tokens.ts +100 -100
- package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -153
- package/server/modules/database/repositories/notification-preferences.ts +117 -117
- package/server/modules/database/repositories/projects.db.ts +196 -196
- package/server/modules/database/repositories/provider-models.ts +168 -168
- package/server/modules/database/repositories/push-subscriptions.ts +80 -80
- package/server/modules/database/repositories/scan-state.db.ts +42 -42
- package/server/modules/database/repositories/sessions.db.ts +524 -524
- package/server/modules/database/repositories/users.ts +140 -140
- package/server/modules/database/repositories/vapid-keys.ts +57 -57
- package/server/modules/database/schema.ts +210 -210
- package/server/modules/database/tests/projects.db.integration.test.ts +72 -72
- package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -125
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +109 -109
- package/server/modules/database/tests/sessions.db.integration.test.ts +116 -116
- package/server/modules/file-tree/file-tree.module.ts +116 -116
- package/server/modules/file-tree/file-tree.routes.ts +258 -258
- package/server/modules/file-tree/file-tree.service.ts +675 -675
- package/server/modules/file-tree/index.ts +2 -2
- package/server/modules/file-tree/tests/file-tree.routes.test.ts +157 -157
- package/server/modules/file-tree/tests/file-tree.service.test.ts +371 -371
- package/server/modules/git/git-branch.service.ts +43 -43
- package/server/modules/git/git-parsing.service.ts +66 -66
- package/server/modules/git/git.module.ts +22 -22
- package/server/modules/git/git.routes.ts +1596 -1596
- package/server/modules/git/index.ts +2 -2
- package/server/modules/git/tests/git-branch.service.test.ts +60 -60
- package/server/modules/git/tests/git-init.routes.test.ts +132 -132
- package/server/modules/git/tests/git.test.ts +106 -106
- package/server/modules/notifications/index.ts +24 -24
- package/server/modules/notifications/notifications.routes.ts +127 -127
- package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -124
- package/server/modules/notifications/services/notification-orchestrator.service.js +310 -310
- package/server/modules/notifications/tests/notification-orchestrator.integration.test.ts +55 -55
- package/server/modules/notifications/vapid-keys.service.ts +38 -38
- package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -109
- package/server/modules/plugins/index.ts +9 -9
- package/server/modules/plugins/plugin-process.service.ts +217 -217
- package/server/modules/plugins/plugin-registry.service.ts +459 -459
- package/server/modules/plugins/plugins.module.ts +33 -33
- package/server/modules/plugins/plugins.routes.ts +66 -66
- package/server/modules/plugins/plugins.service.ts +147 -147
- package/server/modules/plugins/tests/plugins.service.test.ts +32 -32
- package/server/modules/projects/index.ts +11 -11
- package/server/modules/projects/projects.routes.ts +273 -273
- package/server/modules/projects/services/project-clone.service.ts +323 -323
- package/server/modules/projects/services/project-delete.service.ts +90 -90
- package/server/modules/projects/services/project-management.service.ts +144 -144
- package/server/modules/projects/services/project-star.service.ts +78 -78
- package/server/modules/projects/services/projects-has-taskmaster.service.ts +248 -248
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +314 -314
- package/server/modules/projects/tests/project-clone.service.test.ts +183 -183
- package/server/modules/projects/tests/project-management.service.test.ts +117 -117
- package/server/modules/projects/tests/project-star.service.test.ts +123 -123
- package/server/modules/projects/tests/projects-has-taskmaster.service.test.ts +105 -105
- package/server/modules/providers/README.md +380 -380
- package/server/modules/providers/index.ts +10 -10
- package/server/modules/providers/list/claude/claude-auth.provider.ts +160 -160
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
- package/server/modules/providers/list/claude/claude-models.provider.ts +294 -294
- package/server/modules/providers/list/claude/claude-runtime.provider.js +1068 -1068
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +203 -203
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +684 -684
- package/server/modules/providers/list/claude/claude-skills.provider.ts +265 -265
- package/server/modules/providers/list/claude/claude.provider.ts +30 -30
- package/server/modules/providers/list/codex/codex-auth.provider.ts +100 -100
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
- package/server/modules/providers/list/codex/codex-models.provider.ts +123 -123
- package/server/modules/providers/list/codex/codex-runtime.provider.js +533 -533
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +203 -203
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +938 -938
- package/server/modules/providers/list/codex/codex-skills.provider.ts +73 -73
- package/server/modules/providers/list/codex/codex.provider.ts +28 -28
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -143
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +119 -119
- package/server/modules/providers/list/cursor/cursor-runtime.provider.js +385 -385
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +159 -159
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +654 -654
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +39 -39
- package/server/modules/providers/list/cursor/cursor.provider.ts +30 -30
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +110 -110
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -228
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +203 -203
- package/server/modules/providers/list/opencode/opencode-runtime.provider.js +434 -434
- package/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +261 -261
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +179 -179
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +500 -500
- package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -78
- package/server/modules/providers/list/opencode/opencode.provider.ts +30 -30
- package/server/modules/providers/provider.registry.ts +36 -36
- package/server/modules/providers/provider.routes.ts +877 -877
- package/server/modules/providers/services/mcp.service.ts +109 -109
- package/server/modules/providers/services/provider-auth.service.ts +26 -26
- package/server/modules/providers/services/provider-capabilities.service.ts +97 -97
- package/server/modules/providers/services/provider-models.service.ts +401 -401
- package/server/modules/providers/services/provider-runtime.service.ts +108 -108
- package/server/modules/providers/services/provider-token-usage.service.ts +357 -357
- package/server/modules/providers/services/session-conversations-search.service.ts +1321 -1321
- package/server/modules/providers/services/session-synchronizer.service.ts +74 -74
- package/server/modules/providers/services/sessions-watcher.service.ts +247 -247
- package/server/modules/providers/services/sessions.service.ts +471 -471
- package/server/modules/providers/services/skills.service.ts +39 -39
- package/server/modules/providers/shared/base/abstract.provider.ts +32 -32
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
- package/server/modules/providers/shared/skills/skills.provider.ts +287 -287
- package/server/modules/providers/tests/claude-auth.test.ts +150 -150
- package/server/modules/providers/tests/claude-sessions.test.ts +61 -61
- package/server/modules/providers/tests/codex-sessions.test.ts +231 -231
- package/server/modules/providers/tests/mcp.test.ts +349 -349
- package/server/modules/providers/tests/opencode-models.test.ts +41 -41
- package/server/modules/providers/tests/opencode-sessions.test.ts +522 -522
- package/server/modules/providers/tests/provider-attachment-history.test.ts +247 -247
- package/server/modules/providers/tests/provider-models.service.test.ts +353 -353
- package/server/modules/providers/tests/provider-runtime.service.test.ts +138 -138
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +186 -186
- package/server/modules/providers/tests/provider.routes.test.ts +244 -244
- package/server/modules/providers/tests/sessions-details.test.ts +73 -73
- package/server/modules/providers/tests/sessions.service.test.ts +127 -127
- package/server/modules/providers/tests/skills.test.ts +692 -692
- package/server/modules/settings/index.ts +2 -2
- package/server/modules/settings/settings.module.ts +50 -50
- package/server/modules/settings/settings.routes.ts +51 -51
- package/server/modules/settings/settings.service.ts +186 -186
- package/server/modules/settings/tests/settings.service.test.ts +54 -54
- package/server/modules/system/index.ts +2 -2
- package/server/modules/system/system.module.ts +62 -62
- package/server/modules/system/system.routes.ts +21 -21
- package/server/modules/system/system.service.ts +76 -76
- package/server/modules/system/tests/system.service.test.ts +118 -118
- package/server/modules/taskmaster/index.ts +2 -2
- package/server/modules/taskmaster/taskmaster.module.ts +24 -24
- package/server/modules/taskmaster/taskmaster.routes.ts +1482 -1482
- package/server/modules/taskmaster/taskmaster.service.ts +132 -132
- package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +144 -144
- package/server/modules/taskmaster/tests/taskmaster.service.test.ts +97 -97
- package/server/modules/user/index.ts +2 -2
- package/server/modules/user/tests/user.service.test.ts +61 -61
- package/server/modules/user/user.module.ts +58 -58
- package/server/modules/user/user.routes.ts +50 -50
- package/server/modules/user/user.service.ts +85 -85
- package/server/modules/voice/index.ts +2 -2
- package/server/modules/voice/tests/voice.service.test.ts +98 -98
- package/server/modules/voice/voice.module.ts +47 -47
- package/server/modules/voice/voice.routes.ts +113 -113
- package/server/modules/voice/voice.service.ts +192 -192
- package/server/modules/websocket/README.md +273 -273
- package/server/modules/websocket/index.ts +3 -3
- package/server/modules/websocket/services/chat-run-registry.service.ts +343 -343
- package/server/modules/websocket/services/chat-session-writer.service.ts +145 -145
- package/server/modules/websocket/services/chat-websocket.service.ts +425 -425
- package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +65 -65
- package/server/modules/websocket/services/shell-websocket.service.ts +502 -502
- package/server/modules/websocket/services/websocket-auth.service.ts +59 -59
- package/server/modules/websocket/services/websocket-server.service.ts +124 -124
- package/server/modules/websocket/services/websocket-state.service.ts +16 -16
- package/server/modules/websocket/services/websocket-writer.service.ts +38 -38
- package/server/modules/websocket/tests/chat-attachment-filter.test.ts +59 -59
- package/server/modules/websocket/tests/chat-run-registry.test.ts +282 -282
- package/server/modules/websocket/tests/shell-websocket.service.test.ts +100 -100
- package/server/modules/websocket/tests/websocket-heartbeat.service.test.ts +78 -78
- package/server/modules/worktrees/index.ts +2 -2
- package/server/modules/worktrees/services/worktree-create-and-open.service.ts +53 -53
- package/server/modules/worktrees/services/worktree-create.service.ts +104 -104
- package/server/modules/worktrees/services/worktree-git.service.ts +207 -207
- package/server/modules/worktrees/services/worktree-list.service.ts +143 -143
- package/server/modules/worktrees/services/worktree-merge.service.ts +157 -157
- package/server/modules/worktrees/services/worktree-open.service.ts +85 -85
- package/server/modules/worktrees/services/worktree-remove.service.ts +88 -88
- package/server/modules/worktrees/tests/worktree-create-and-open.service.test.ts +68 -68
- package/server/modules/worktrees/tests/worktree-create.service.test.ts +142 -142
- package/server/modules/worktrees/tests/worktree-git.service.test.ts +84 -84
- package/server/modules/worktrees/tests/worktree-list.service.test.ts +45 -45
- package/server/modules/worktrees/tests/worktree-merge.service.test.ts +213 -213
- package/server/modules/worktrees/tests/worktree-open.service.test.ts +122 -122
- package/server/modules/worktrees/tests/worktree-remove.service.test.ts +227 -227
- package/server/modules/worktrees/tests/worktrees.routes.test.ts +199 -199
- package/server/modules/worktrees/worktrees.module.ts +113 -113
- package/server/modules/worktrees/worktrees.routes.ts +116 -116
- package/server/shared/claude-cli-path.ts +139 -139
- package/server/shared/frontmatter.ts +18 -18
- package/server/shared/image-attachments.ts +435 -435
- package/server/shared/interfaces.ts +179 -179
- package/server/shared/tests/claude-cli-path.test.ts +61 -61
- package/server/shared/tests/image-attachments.test.ts +353 -353
- package/server/shared/tests/slice-tail-page.test.ts +42 -42
- package/server/shared/types.ts +1226 -1226
- package/server/shared/utils.ts +1161 -1161
- package/server/tsconfig.json +40 -40
- package/shared/networkHosts.js +22 -22
- package/dist/assets/channel-Bm0ZLE4z.js +0 -1
- package/dist/assets/classDiagram-JCYQIIEL-DqEOcNxw.js +0 -1
- package/dist/assets/classDiagram-v2-OCEON4UE-DqEOcNxw.js +0 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DARLpEFJ.js +0 -1
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-CrgDgaY8.js +0 -8
|
@@ -1,153 +1,153 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import crypto from 'node:crypto';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
|
|
6
|
-
import pty, { type IPty } from 'node-pty';
|
|
7
|
-
import { WebSocket, type RawData } from 'ws';
|
|
8
|
-
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
import pty, { type IPty } from 'node-pty';
|
|
7
|
+
import { WebSocket, type RawData } from 'ws';
|
|
8
|
+
|
|
9
9
|
import { parseIncomingJsonObject } from '@/shared/utils.js';
|
|
10
10
|
import { terminalReplayStore } from '@/modules/websocket/services/terminal-replay-store.js';
|
|
11
|
-
|
|
12
|
-
type ShellIncomingMessage = {
|
|
13
|
-
type?: string;
|
|
14
|
-
data?: string;
|
|
15
|
-
cols?: number;
|
|
16
|
-
rows?: number;
|
|
17
|
-
projectPath?: string;
|
|
18
|
-
sessionId?: string;
|
|
19
|
-
hasSession?: boolean;
|
|
20
|
-
provider?: string;
|
|
21
|
-
initialCommand?: string;
|
|
22
|
-
isPlainShell?: boolean;
|
|
11
|
+
|
|
12
|
+
type ShellIncomingMessage = {
|
|
13
|
+
type?: string;
|
|
14
|
+
data?: string;
|
|
15
|
+
cols?: number;
|
|
16
|
+
rows?: number;
|
|
17
|
+
projectPath?: string;
|
|
18
|
+
sessionId?: string;
|
|
19
|
+
hasSession?: boolean;
|
|
20
|
+
provider?: string;
|
|
21
|
+
initialCommand?: string;
|
|
22
|
+
isPlainShell?: boolean;
|
|
23
23
|
forceRestart?: boolean;
|
|
24
24
|
shellMode?: string;
|
|
25
25
|
};
|
|
26
|
-
|
|
27
|
-
type PtySessionEntry = {
|
|
26
|
+
|
|
27
|
+
type PtySessionEntry = {
|
|
28
28
|
pty: IPty;
|
|
29
29
|
ws: WebSocket | null;
|
|
30
30
|
timeoutId: NodeJS.Timeout | null;
|
|
31
|
-
projectPath: string;
|
|
32
|
-
sessionId: string | null;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const ptySessionsMap = new Map<string, PtySessionEntry>();
|
|
36
|
-
const PTY_SESSION_TIMEOUT = 30 * 60 * 1000;
|
|
37
|
-
const SHELL_URL_PARSE_BUFFER_LIMIT = 32768;
|
|
38
|
-
const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
|
|
31
|
+
projectPath: string;
|
|
32
|
+
sessionId: string | null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const ptySessionsMap = new Map<string, PtySessionEntry>();
|
|
36
|
+
const PTY_SESSION_TIMEOUT = 30 * 60 * 1000;
|
|
37
|
+
const SHELL_URL_PARSE_BUFFER_LIMIT = 32768;
|
|
38
|
+
const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
|
|
39
39
|
const TRAILING_URL_PUNCTUATION_REGEX = /[)\]}>.,;:!?]+$/;
|
|
40
40
|
const TERMINAL_REPLAY_TRUNCATED_MESSAGE =
|
|
41
41
|
'\x1b[33m[Earlier terminal output was omitted to limit memory usage]\x1b[0m\r\n';
|
|
42
|
-
|
|
43
|
-
function stripAnsiSequences(value: string): string {
|
|
44
|
-
return value.replace(ANSI_ESCAPE_SEQUENCE_REGEX, '');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function normalizeDetectedUrl(url: string): string | null {
|
|
48
|
-
const cleanedUrl = url.trim().replace(TRAILING_URL_PUNCTUATION_REGEX, '');
|
|
49
|
-
if (!cleanedUrl) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const parsedUrl = new URL(cleanedUrl);
|
|
55
|
-
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
return parsedUrl.toString();
|
|
59
|
-
} catch {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function extractUrlsFromText(value: string): string[] {
|
|
65
|
-
const directMatches = value.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/gi) ?? [];
|
|
66
|
-
|
|
67
|
-
// Terminal width can split a URL across lines, so valid URL characters on
|
|
68
|
-
// immediately following lines are joined before the URL is validated.
|
|
69
|
-
const wrappedMatches: string[] = [];
|
|
70
|
-
const urlContinuationPattern = /^[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$/;
|
|
71
|
-
const lines = value.split(/\r?\n/);
|
|
72
|
-
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
73
|
-
const line = lines[lineIndex].trim();
|
|
74
|
-
const startMatch = line.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/i);
|
|
75
|
-
if (!startMatch) {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let combinedUrl = startMatch[0];
|
|
80
|
-
let continuationIndex = lineIndex + 1;
|
|
81
|
-
while (continuationIndex < lines.length) {
|
|
82
|
-
const continuation = lines[continuationIndex].trim();
|
|
83
|
-
if (!continuation || !urlContinuationPattern.test(continuation)) {
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
combinedUrl += continuation;
|
|
87
|
-
continuationIndex += 1;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
wrappedMatches.push(combinedUrl);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return Array.from(new Set([...directMatches, ...wrappedMatches]));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function shouldAutoOpenUrlFromOutput(value: string): boolean {
|
|
97
|
-
const normalizedOutput = value.toLowerCase();
|
|
98
|
-
return (
|
|
99
|
-
normalizedOutput.includes("browser didn't open") ||
|
|
100
|
-
normalizedOutput.includes('open this url') ||
|
|
101
|
-
normalizedOutput.includes('continue in your browser') ||
|
|
102
|
-
normalizedOutput.includes('press enter to open') ||
|
|
103
|
-
normalizedOutput.includes('open_url:')
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
42
|
+
|
|
43
|
+
function stripAnsiSequences(value: string): string {
|
|
44
|
+
return value.replace(ANSI_ESCAPE_SEQUENCE_REGEX, '');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeDetectedUrl(url: string): string | null {
|
|
48
|
+
const cleanedUrl = url.trim().replace(TRAILING_URL_PUNCTUATION_REGEX, '');
|
|
49
|
+
if (!cleanedUrl) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const parsedUrl = new URL(cleanedUrl);
|
|
55
|
+
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return parsedUrl.toString();
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function extractUrlsFromText(value: string): string[] {
|
|
65
|
+
const directMatches = value.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/gi) ?? [];
|
|
66
|
+
|
|
67
|
+
// Terminal width can split a URL across lines, so valid URL characters on
|
|
68
|
+
// immediately following lines are joined before the URL is validated.
|
|
69
|
+
const wrappedMatches: string[] = [];
|
|
70
|
+
const urlContinuationPattern = /^[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$/;
|
|
71
|
+
const lines = value.split(/\r?\n/);
|
|
72
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
73
|
+
const line = lines[lineIndex].trim();
|
|
74
|
+
const startMatch = line.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/i);
|
|
75
|
+
if (!startMatch) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let combinedUrl = startMatch[0];
|
|
80
|
+
let continuationIndex = lineIndex + 1;
|
|
81
|
+
while (continuationIndex < lines.length) {
|
|
82
|
+
const continuation = lines[continuationIndex].trim();
|
|
83
|
+
if (!continuation || !urlContinuationPattern.test(continuation)) {
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
combinedUrl += continuation;
|
|
87
|
+
continuationIndex += 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
wrappedMatches.push(combinedUrl);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return Array.from(new Set([...directMatches, ...wrappedMatches]));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function shouldAutoOpenUrlFromOutput(value: string): boolean {
|
|
97
|
+
const normalizedOutput = value.toLowerCase();
|
|
98
|
+
return (
|
|
99
|
+
normalizedOutput.includes("browser didn't open") ||
|
|
100
|
+
normalizedOutput.includes('open this url') ||
|
|
101
|
+
normalizedOutput.includes('continue in your browser') ||
|
|
102
|
+
normalizedOutput.includes('press enter to open') ||
|
|
103
|
+
normalizedOutput.includes('open_url:')
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
107
|
type ShellWebSocketDependencies = {
|
|
108
|
-
resolveProviderSessionId: (
|
|
109
|
-
sessionId: string,
|
|
110
|
-
provider: string,
|
|
108
|
+
resolveProviderSessionId: (
|
|
109
|
+
sessionId: string,
|
|
110
|
+
provider: string,
|
|
111
111
|
) => string | null | undefined;
|
|
112
112
|
spawnPty?: typeof pty.spawn;
|
|
113
113
|
platform?: () => NodeJS.Platform;
|
|
114
114
|
environment?: NodeJS.ProcessEnv;
|
|
115
115
|
};
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Reads a string field from untyped payloads and falls back when absent.
|
|
119
|
-
*/
|
|
120
|
-
function readString(value: unknown, fallback = ''): string {
|
|
121
|
-
return typeof value === 'string' ? value : fallback;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Reads a boolean field from untyped payloads and falls back when absent.
|
|
126
|
-
*/
|
|
127
|
-
function readBoolean(value: unknown, fallback = false): boolean {
|
|
128
|
-
return typeof value === 'boolean' ? value : fallback;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Reads a finite number field from untyped payloads and falls back when absent.
|
|
133
|
-
*/
|
|
134
|
-
function readNumber(value: unknown, fallback: number): number {
|
|
135
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Parses incoming websocket shell messages and keeps processing safe when
|
|
140
|
-
* malformed payloads are received.
|
|
141
|
-
*/
|
|
142
|
-
function parseShellMessage(rawMessage: RawData): ShellIncomingMessage | null {
|
|
143
|
-
const payload = parseIncomingJsonObject(rawMessage);
|
|
144
|
-
if (!payload) {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return payload as ShellIncomingMessage;
|
|
149
|
-
}
|
|
150
|
-
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Reads a string field from untyped payloads and falls back when absent.
|
|
119
|
+
*/
|
|
120
|
+
function readString(value: unknown, fallback = ''): string {
|
|
121
|
+
return typeof value === 'string' ? value : fallback;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Reads a boolean field from untyped payloads and falls back when absent.
|
|
126
|
+
*/
|
|
127
|
+
function readBoolean(value: unknown, fallback = false): boolean {
|
|
128
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Reads a finite number field from untyped payloads and falls back when absent.
|
|
133
|
+
*/
|
|
134
|
+
function readNumber(value: unknown, fallback: number): number {
|
|
135
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Parses incoming websocket shell messages and keeps processing safe when
|
|
140
|
+
* malformed payloads are received.
|
|
141
|
+
*/
|
|
142
|
+
function parseShellMessage(rawMessage: RawData): ShellIncomingMessage | null {
|
|
143
|
+
const payload = parseIncomingJsonObject(rawMessage);
|
|
144
|
+
if (!payload) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return payload as ShellIncomingMessage;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
151
|
const SAFE_SESSION_ID_PATTERN = /^[a-zA-Z0-9_.\-:]+$/;
|
|
152
152
|
const BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
153
153
|
const MAX_TERMINAL_BINARY_INPUT_BYTES = 1024;
|
|
@@ -158,63 +158,63 @@ const HERDR_PANE_ENVIRONMENT_KEYS = [
|
|
|
158
158
|
'HERDR_TAB_ID',
|
|
159
159
|
'HERDR_WORKSPACE_ID',
|
|
160
160
|
] as const;
|
|
161
|
-
|
|
162
|
-
function resolveResumeSessionId(
|
|
163
|
-
message: ShellIncomingMessage,
|
|
164
|
-
dependencies: ShellWebSocketDependencies
|
|
165
|
-
): string {
|
|
166
|
-
const hasSession = readBoolean(message.hasSession);
|
|
167
|
-
const sessionId = readString(message.sessionId);
|
|
168
|
-
const provider = readString(message.provider, 'claude');
|
|
169
|
-
|
|
170
|
-
if (!hasSession || !sessionId) {
|
|
171
|
-
return '';
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
let resumeSessionId: string | null | undefined;
|
|
175
|
-
try {
|
|
176
|
-
resumeSessionId = dependencies.resolveProviderSessionId(sessionId, provider);
|
|
177
|
-
} catch (error) {
|
|
178
|
-
console.error('Failed to resolve provider session ID:', error);
|
|
179
|
-
resumeSessionId = undefined;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const resolvedSessionId = resumeSessionId === undefined ? sessionId : resumeSessionId;
|
|
183
|
-
if (!resolvedSessionId || !SAFE_SESSION_ID_PATTERN.test(resolvedSessionId)) {
|
|
184
|
-
return '';
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return resolvedSessionId;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Resolves provider command line for plain shell and agent-backed shell modes.
|
|
192
|
-
*/
|
|
161
|
+
|
|
162
|
+
function resolveResumeSessionId(
|
|
163
|
+
message: ShellIncomingMessage,
|
|
164
|
+
dependencies: ShellWebSocketDependencies
|
|
165
|
+
): string {
|
|
166
|
+
const hasSession = readBoolean(message.hasSession);
|
|
167
|
+
const sessionId = readString(message.sessionId);
|
|
168
|
+
const provider = readString(message.provider, 'claude');
|
|
169
|
+
|
|
170
|
+
if (!hasSession || !sessionId) {
|
|
171
|
+
return '';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let resumeSessionId: string | null | undefined;
|
|
175
|
+
try {
|
|
176
|
+
resumeSessionId = dependencies.resolveProviderSessionId(sessionId, provider);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error('Failed to resolve provider session ID:', error);
|
|
179
|
+
resumeSessionId = undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const resolvedSessionId = resumeSessionId === undefined ? sessionId : resumeSessionId;
|
|
183
|
+
if (!resolvedSessionId || !SAFE_SESSION_ID_PATTERN.test(resolvedSessionId)) {
|
|
184
|
+
return '';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return resolvedSessionId;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Resolves provider command line for plain shell and agent-backed shell modes.
|
|
192
|
+
*/
|
|
193
193
|
function buildShellCommand(
|
|
194
194
|
message: ShellIncomingMessage,
|
|
195
195
|
dependencies: ShellWebSocketDependencies
|
|
196
196
|
): string {
|
|
197
|
-
const hasSession = readBoolean(message.hasSession);
|
|
198
|
-
const initialCommand = readString(message.initialCommand);
|
|
197
|
+
const hasSession = readBoolean(message.hasSession);
|
|
198
|
+
const initialCommand = readString(message.initialCommand);
|
|
199
199
|
const provider = readString(message.provider, 'claude');
|
|
200
200
|
const resumeSessionId = resolveResumeSessionId(message, dependencies);
|
|
201
201
|
const platform = dependencies.platform?.() ?? os.platform();
|
|
202
|
-
const isPlainShell =
|
|
203
|
-
readBoolean(message.isPlainShell) ||
|
|
204
|
-
(!!initialCommand && !hasSession) ||
|
|
205
|
-
provider === 'plain-shell';
|
|
206
|
-
|
|
207
|
-
if (isPlainShell) {
|
|
208
|
-
return initialCommand;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (provider === 'cursor') {
|
|
212
|
-
if (resumeSessionId) {
|
|
213
|
-
return `cursor-agent --resume="${resumeSessionId}"`;
|
|
214
|
-
}
|
|
215
|
-
return 'cursor-agent';
|
|
216
|
-
}
|
|
217
|
-
|
|
202
|
+
const isPlainShell =
|
|
203
|
+
readBoolean(message.isPlainShell) ||
|
|
204
|
+
(!!initialCommand && !hasSession) ||
|
|
205
|
+
provider === 'plain-shell';
|
|
206
|
+
|
|
207
|
+
if (isPlainShell) {
|
|
208
|
+
return initialCommand;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (provider === 'cursor') {
|
|
212
|
+
if (resumeSessionId) {
|
|
213
|
+
return `cursor-agent --resume="${resumeSessionId}"`;
|
|
214
|
+
}
|
|
215
|
+
return 'cursor-agent';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
218
|
if (provider === 'codex') {
|
|
219
219
|
const codexCommand = platform === 'win32' ? 'codex.cmd' : 'codex';
|
|
220
220
|
if (resumeSessionId) {
|
|
@@ -224,78 +224,78 @@ function buildShellCommand(
|
|
|
224
224
|
return `${codexCommand} resume "${resumeSessionId}" || ${codexCommand}`;
|
|
225
225
|
}
|
|
226
226
|
return codexCommand;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (provider === 'opencode') {
|
|
230
|
-
if (resumeSessionId) {
|
|
231
|
-
return `opencode --session "${resumeSessionId}"`;
|
|
232
|
-
}
|
|
233
|
-
return initialCommand || 'opencode';
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const command = initialCommand || 'claude';
|
|
237
|
-
if (resumeSessionId) {
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (provider === 'opencode') {
|
|
230
|
+
if (resumeSessionId) {
|
|
231
|
+
return `opencode --session "${resumeSessionId}"`;
|
|
232
|
+
}
|
|
233
|
+
return initialCommand || 'opencode';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const command = initialCommand || 'claude';
|
|
237
|
+
if (resumeSessionId) {
|
|
238
238
|
if (platform === 'win32') {
|
|
239
|
-
return `claude --resume "${resumeSessionId}"; if ($LASTEXITCODE -ne 0) { claude }`;
|
|
240
|
-
}
|
|
241
|
-
return `claude --resume "${resumeSessionId}" || claude`;
|
|
242
|
-
}
|
|
243
|
-
return command;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function readEnvValue(env: NodeJS.ProcessEnv, key: string): string | undefined {
|
|
247
|
-
const resolvedKey = Object.keys(env).find((envKey) => envKey.toLowerCase() === key.toLowerCase());
|
|
248
|
-
return resolvedKey ? env[resolvedKey] : undefined;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function getPathEnvKey(env: NodeJS.ProcessEnv): string {
|
|
252
|
-
return Object.keys(env).find((key) => key.toLowerCase() === 'path') || 'PATH';
|
|
253
|
-
}
|
|
254
|
-
|
|
239
|
+
return `claude --resume "${resumeSessionId}"; if ($LASTEXITCODE -ne 0) { claude }`;
|
|
240
|
+
}
|
|
241
|
+
return `claude --resume "${resumeSessionId}" || claude`;
|
|
242
|
+
}
|
|
243
|
+
return command;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function readEnvValue(env: NodeJS.ProcessEnv, key: string): string | undefined {
|
|
247
|
+
const resolvedKey = Object.keys(env).find((envKey) => envKey.toLowerCase() === key.toLowerCase());
|
|
248
|
+
return resolvedKey ? env[resolvedKey] : undefined;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function getPathEnvKey(env: NodeJS.ProcessEnv): string {
|
|
252
|
+
return Object.keys(env).find((key) => key.toLowerCase() === 'path') || 'PATH';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
255
|
function prioritizeUserNpmGlobalBin(env: NodeJS.ProcessEnv): { key: string; value: string | undefined } {
|
|
256
|
-
const pathKey = getPathEnvKey(env);
|
|
257
|
-
const currentPath = env[pathKey];
|
|
258
|
-
if (!currentPath) {
|
|
259
|
-
return { key: pathKey, value: currentPath };
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const delimiter = path.delimiter;
|
|
263
|
-
const pathEntries = currentPath.split(delimiter).filter(Boolean);
|
|
264
|
-
const npmPrefix = readEnvValue(env, 'npm_config_prefix');
|
|
265
|
-
const appData = readEnvValue(env, 'APPDATA');
|
|
266
|
-
const candidates = [
|
|
267
|
-
npmPrefix || '',
|
|
268
|
-
npmPrefix ? path.join(npmPrefix, 'bin') : '',
|
|
269
|
-
appData ? path.join(appData, 'npm') : '',
|
|
270
|
-
path.join(os.homedir(), 'AppData', 'Roaming', 'npm'),
|
|
271
|
-
path.join(os.homedir(), '.npm-global', 'bin'),
|
|
272
|
-
].filter(Boolean);
|
|
273
|
-
|
|
274
|
-
const normalizedPathEntries = pathEntries.map((entry) => os.platform() === 'win32' ? entry.toLowerCase() : entry);
|
|
275
|
-
const preferredEntries = candidates.filter((candidate, index) => {
|
|
276
|
-
const normalizedCandidate = os.platform() === 'win32' ? candidate.toLowerCase() : candidate;
|
|
277
|
-
return (
|
|
278
|
-
candidates.indexOf(candidate) === index &&
|
|
279
|
-
normalizedPathEntries.includes(normalizedCandidate)
|
|
280
|
-
);
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
if (preferredEntries.length === 0) {
|
|
284
|
-
return { key: pathKey, value: currentPath };
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const normalizedPreferredEntries = preferredEntries.map((entry) =>
|
|
288
|
-
os.platform() === 'win32' ? entry.toLowerCase() : entry
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
const value = [
|
|
292
|
-
...preferredEntries,
|
|
293
|
-
...pathEntries.filter((entry) => {
|
|
294
|
-
const normalizedEntry = os.platform() === 'win32' ? entry.toLowerCase() : entry;
|
|
295
|
-
return !normalizedPreferredEntries.includes(normalizedEntry);
|
|
296
|
-
}),
|
|
297
|
-
].join(delimiter);
|
|
298
|
-
|
|
256
|
+
const pathKey = getPathEnvKey(env);
|
|
257
|
+
const currentPath = env[pathKey];
|
|
258
|
+
if (!currentPath) {
|
|
259
|
+
return { key: pathKey, value: currentPath };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const delimiter = path.delimiter;
|
|
263
|
+
const pathEntries = currentPath.split(delimiter).filter(Boolean);
|
|
264
|
+
const npmPrefix = readEnvValue(env, 'npm_config_prefix');
|
|
265
|
+
const appData = readEnvValue(env, 'APPDATA');
|
|
266
|
+
const candidates = [
|
|
267
|
+
npmPrefix || '',
|
|
268
|
+
npmPrefix ? path.join(npmPrefix, 'bin') : '',
|
|
269
|
+
appData ? path.join(appData, 'npm') : '',
|
|
270
|
+
path.join(os.homedir(), 'AppData', 'Roaming', 'npm'),
|
|
271
|
+
path.join(os.homedir(), '.npm-global', 'bin'),
|
|
272
|
+
].filter(Boolean);
|
|
273
|
+
|
|
274
|
+
const normalizedPathEntries = pathEntries.map((entry) => os.platform() === 'win32' ? entry.toLowerCase() : entry);
|
|
275
|
+
const preferredEntries = candidates.filter((candidate, index) => {
|
|
276
|
+
const normalizedCandidate = os.platform() === 'win32' ? candidate.toLowerCase() : candidate;
|
|
277
|
+
return (
|
|
278
|
+
candidates.indexOf(candidate) === index &&
|
|
279
|
+
normalizedPathEntries.includes(normalizedCandidate)
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (preferredEntries.length === 0) {
|
|
284
|
+
return { key: pathKey, value: currentPath };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const normalizedPreferredEntries = preferredEntries.map((entry) =>
|
|
288
|
+
os.platform() === 'win32' ? entry.toLowerCase() : entry
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const value = [
|
|
292
|
+
...preferredEntries,
|
|
293
|
+
...pathEntries.filter((entry) => {
|
|
294
|
+
const normalizedEntry = os.platform() === 'win32' ? entry.toLowerCase() : entry;
|
|
295
|
+
return !normalizedPreferredEntries.includes(normalizedEntry);
|
|
296
|
+
}),
|
|
297
|
+
].join(delimiter);
|
|
298
|
+
|
|
299
299
|
return { key: pathKey, value };
|
|
300
300
|
}
|
|
301
301
|
|
|
@@ -376,53 +376,53 @@ function buildPtySessionKey({
|
|
|
376
376
|
});
|
|
377
377
|
return `pty:${crypto.createHash('sha256').update(identity).digest('hex')}`;
|
|
378
378
|
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Used by this module's websocket gateway to connect the standalone Shell UI
|
|
382
|
-
* to a retained PTY while keeping process lifecycle ownership on the server.
|
|
383
|
-
*/
|
|
384
|
-
export function handleShellConnection(
|
|
385
|
-
ws: WebSocket,
|
|
386
|
-
dependencies: ShellWebSocketDependencies
|
|
387
|
-
): void {
|
|
388
|
-
console.log('[INFO] Shell websocket connected');
|
|
389
|
-
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Used by this module's websocket gateway to connect the standalone Shell UI
|
|
382
|
+
* to a retained PTY while keeping process lifecycle ownership on the server.
|
|
383
|
+
*/
|
|
384
|
+
export function handleShellConnection(
|
|
385
|
+
ws: WebSocket,
|
|
386
|
+
dependencies: ShellWebSocketDependencies
|
|
387
|
+
): void {
|
|
388
|
+
console.log('[INFO] Shell websocket connected');
|
|
389
|
+
|
|
390
390
|
let shellProcess: IPty | null = null;
|
|
391
391
|
let ptySessionKey: string | null = null;
|
|
392
392
|
let isHerdrConnection = false;
|
|
393
|
-
let urlDetectionBuffer = '';
|
|
394
|
-
const announcedAuthUrls = new Set<string>();
|
|
395
|
-
|
|
396
|
-
ws.on('message', async (rawMessage) => {
|
|
397
|
-
try {
|
|
398
|
-
const data = parseShellMessage(rawMessage);
|
|
399
|
-
if (!data?.type) {
|
|
400
|
-
throw new Error('Invalid websocket payload');
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (data.type === 'init') {
|
|
404
|
-
const projectPath = readString(data.projectPath, process.cwd());
|
|
405
|
-
const sessionId = readString(data.sessionId) || null;
|
|
406
|
-
const hasSession = readBoolean(data.hasSession);
|
|
407
|
-
const provider = readString(data.provider, 'claude');
|
|
408
|
-
const initialCommand = readString(data.initialCommand);
|
|
393
|
+
let urlDetectionBuffer = '';
|
|
394
|
+
const announcedAuthUrls = new Set<string>();
|
|
395
|
+
|
|
396
|
+
ws.on('message', async (rawMessage) => {
|
|
397
|
+
try {
|
|
398
|
+
const data = parseShellMessage(rawMessage);
|
|
399
|
+
if (!data?.type) {
|
|
400
|
+
throw new Error('Invalid websocket payload');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (data.type === 'init') {
|
|
404
|
+
const projectPath = readString(data.projectPath, process.cwd());
|
|
405
|
+
const sessionId = readString(data.sessionId) || null;
|
|
406
|
+
const hasSession = readBoolean(data.hasSession);
|
|
407
|
+
const provider = readString(data.provider, 'claude');
|
|
408
|
+
const initialCommand = readString(data.initialCommand);
|
|
409
409
|
const forceRestart = readBoolean(data.forceRestart);
|
|
410
410
|
const isHerdrMode = readString(data.shellMode) === 'herdr';
|
|
411
411
|
isHerdrConnection = isHerdrMode;
|
|
412
412
|
const isPlainShell =
|
|
413
|
-
readBoolean(data.isPlainShell) ||
|
|
414
|
-
(!!initialCommand && !hasSession) ||
|
|
415
|
-
provider === 'plain-shell';
|
|
416
|
-
|
|
417
|
-
urlDetectionBuffer = '';
|
|
418
|
-
announcedAuthUrls.clear();
|
|
419
|
-
|
|
420
|
-
const isLoginCommand =
|
|
421
|
-
!!initialCommand &&
|
|
422
|
-
(initialCommand.includes('setup-token') ||
|
|
423
|
-
initialCommand.includes('cursor-agent login') ||
|
|
424
|
-
initialCommand.includes('auth login'));
|
|
425
|
-
|
|
413
|
+
readBoolean(data.isPlainShell) ||
|
|
414
|
+
(!!initialCommand && !hasSession) ||
|
|
415
|
+
provider === 'plain-shell';
|
|
416
|
+
|
|
417
|
+
urlDetectionBuffer = '';
|
|
418
|
+
announcedAuthUrls.clear();
|
|
419
|
+
|
|
420
|
+
const isLoginCommand =
|
|
421
|
+
!!initialCommand &&
|
|
422
|
+
(initialCommand.includes('setup-token') ||
|
|
423
|
+
initialCommand.includes('cursor-agent login') ||
|
|
424
|
+
initialCommand.includes('auth login'));
|
|
425
|
+
|
|
426
426
|
const resolvedProjectPath = path.resolve(projectPath);
|
|
427
427
|
try {
|
|
428
428
|
const stats = fs.statSync(resolvedProjectPath);
|
|
@@ -446,25 +446,25 @@ export function handleShellConnection(
|
|
|
446
446
|
shellMode: readString(data.shellMode),
|
|
447
447
|
initialCommand: isPlainShell ? initialCommand : '',
|
|
448
448
|
});
|
|
449
|
-
|
|
450
|
-
if (isLoginCommand || forceRestart) {
|
|
451
|
-
const oldSession = ptySessionsMap.get(ptySessionKey);
|
|
452
|
-
if (oldSession) {
|
|
453
|
-
if (oldSession.timeoutId) {
|
|
454
|
-
clearTimeout(oldSession.timeoutId);
|
|
449
|
+
|
|
450
|
+
if (isLoginCommand || forceRestart) {
|
|
451
|
+
const oldSession = ptySessionsMap.get(ptySessionKey);
|
|
452
|
+
if (oldSession) {
|
|
453
|
+
if (oldSession.timeoutId) {
|
|
454
|
+
clearTimeout(oldSession.timeoutId);
|
|
455
455
|
}
|
|
456
456
|
terminalReplayStore.delete(ptySessionKey);
|
|
457
457
|
oldSession.pty.kill();
|
|
458
|
-
ptySessionsMap.delete(ptySessionKey);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
const existingSession =
|
|
463
|
-
isLoginCommand || forceRestart ? null : ptySessionsMap.get(ptySessionKey);
|
|
458
|
+
ptySessionsMap.delete(ptySessionKey);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const existingSession =
|
|
463
|
+
isLoginCommand || forceRestart ? null : ptySessionsMap.get(ptySessionKey);
|
|
464
464
|
if (existingSession) {
|
|
465
465
|
shellProcess = existingSession.pty;
|
|
466
|
-
if (existingSession.timeoutId) {
|
|
467
|
-
clearTimeout(existingSession.timeoutId);
|
|
466
|
+
if (existingSession.timeoutId) {
|
|
467
|
+
clearTimeout(existingSession.timeoutId);
|
|
468
468
|
existingSession.timeoutId = null;
|
|
469
469
|
}
|
|
470
470
|
|
|
@@ -487,31 +487,31 @@ export function handleShellConnection(
|
|
|
487
487
|
}
|
|
488
488
|
|
|
489
489
|
ws.send(
|
|
490
|
-
JSON.stringify({
|
|
491
|
-
type: 'output',
|
|
492
|
-
data: '\x1b[36m[Reconnected to existing session]\x1b[0m\r\n',
|
|
493
|
-
})
|
|
494
|
-
);
|
|
495
|
-
|
|
490
|
+
JSON.stringify({
|
|
491
|
+
type: 'output',
|
|
492
|
+
data: '\x1b[36m[Reconnected to existing session]\x1b[0m\r\n',
|
|
493
|
+
})
|
|
494
|
+
);
|
|
495
|
+
|
|
496
496
|
const replay = terminalReplayStore.snapshot(ptySessionKey);
|
|
497
497
|
if (replay.truncated) {
|
|
498
498
|
ws.send(JSON.stringify({ type: 'output', data: TERMINAL_REPLAY_TRUNCATED_MESSAGE }));
|
|
499
499
|
}
|
|
500
500
|
if (replay.chunks.length > 0) {
|
|
501
501
|
replay.chunks.forEach((bufferedData) => {
|
|
502
|
-
ws.send(
|
|
503
|
-
JSON.stringify({
|
|
504
|
-
type: 'output',
|
|
505
|
-
data: bufferedData,
|
|
506
|
-
})
|
|
507
|
-
);
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
|
|
502
|
+
ws.send(
|
|
503
|
+
JSON.stringify({
|
|
504
|
+
type: 'output',
|
|
505
|
+
data: bufferedData,
|
|
506
|
+
})
|
|
507
|
+
);
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
511
|
existingSession.pty.resize(reconnectCols, reconnectRows);
|
|
512
512
|
return;
|
|
513
513
|
}
|
|
514
|
-
|
|
514
|
+
|
|
515
515
|
const shellCommand = buildShellCommand(data, dependencies);
|
|
516
516
|
const resumeSessionId = resolveResumeSessionId(data, dependencies);
|
|
517
517
|
const platform = dependencies.platform?.() ?? os.platform();
|
|
@@ -528,153 +528,153 @@ export function handleShellConnection(
|
|
|
528
528
|
: platform === 'win32'
|
|
529
529
|
? ['-Command', shellCommand]
|
|
530
530
|
: ['-c', shellCommand];
|
|
531
|
-
const termCols = readNumber(data.cols, 80);
|
|
532
|
-
const termRows = readNumber(data.rows, 24);
|
|
531
|
+
const termCols = readNumber(data.cols, 80);
|
|
532
|
+
const termRows = readNumber(data.rows, 24);
|
|
533
533
|
const shellEnvironment = buildShellEnvironment(data, dependencies);
|
|
534
|
-
|
|
535
|
-
shellProcess = (dependencies.spawnPty ?? pty.spawn)(shell, shellArgs, {
|
|
536
|
-
name: 'xterm-256color',
|
|
537
|
-
cols: termCols,
|
|
538
|
-
rows: termRows,
|
|
539
|
-
cwd: resolvedProjectPath,
|
|
534
|
+
|
|
535
|
+
shellProcess = (dependencies.spawnPty ?? pty.spawn)(shell, shellArgs, {
|
|
536
|
+
name: 'xterm-256color',
|
|
537
|
+
cols: termCols,
|
|
538
|
+
rows: termRows,
|
|
539
|
+
cwd: resolvedProjectPath,
|
|
540
540
|
env: shellEnvironment,
|
|
541
|
-
});
|
|
542
|
-
|
|
541
|
+
});
|
|
542
|
+
|
|
543
543
|
ptySessionsMap.set(ptySessionKey, {
|
|
544
544
|
pty: shellProcess,
|
|
545
545
|
ws,
|
|
546
546
|
timeoutId: null,
|
|
547
|
-
projectPath,
|
|
548
|
-
sessionId,
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
shellProcess.onData((chunk) => {
|
|
552
|
-
if (!ptySessionKey) {
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const session = ptySessionsMap.get(ptySessionKey);
|
|
557
|
-
if (!session) {
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
|
|
547
|
+
projectPath,
|
|
548
|
+
sessionId,
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
shellProcess.onData((chunk) => {
|
|
552
|
+
if (!ptySessionKey) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const session = ptySessionsMap.get(ptySessionKey);
|
|
557
|
+
if (!session) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
561
|
if (!isHerdrMode) {
|
|
562
562
|
terminalReplayStore.append(ptySessionKey, chunk);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
if (session.ws && session.ws.readyState === WebSocket.OPEN) {
|
|
566
|
-
let outputData = chunk;
|
|
567
|
-
const cleanChunk = stripAnsiSequences(chunk);
|
|
568
|
-
urlDetectionBuffer = `${urlDetectionBuffer}${cleanChunk}`.slice(-SHELL_URL_PARSE_BUFFER_LIMIT);
|
|
569
|
-
|
|
570
|
-
outputData = outputData.replace(
|
|
571
|
-
/OPEN_URL:\s*(https?:\/\/[^\s\x1b\x07]+)/g,
|
|
572
|
-
'[INFO] Opening in browser: $1'
|
|
573
|
-
);
|
|
574
|
-
|
|
575
|
-
const emitAuthUrl = (detectedUrl: string, autoOpen = false) => {
|
|
576
|
-
const normalizedUrl = normalizeDetectedUrl(detectedUrl);
|
|
577
|
-
if (!normalizedUrl) {
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const isNewUrl = !announcedAuthUrls.has(normalizedUrl);
|
|
582
|
-
if (isNewUrl) {
|
|
583
|
-
announcedAuthUrls.add(normalizedUrl);
|
|
584
|
-
session.ws?.send(
|
|
585
|
-
JSON.stringify({
|
|
586
|
-
type: 'auth_url',
|
|
587
|
-
url: normalizedUrl,
|
|
588
|
-
autoOpen,
|
|
589
|
-
})
|
|
590
|
-
);
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
const normalizedDetectedUrls = extractUrlsFromText(urlDetectionBuffer)
|
|
595
|
-
.map((url) => normalizeDetectedUrl(url))
|
|
596
|
-
.filter((url): url is string => Boolean(url));
|
|
597
|
-
|
|
598
|
-
const dedupedDetectedUrls = Array.from(new Set(normalizedDetectedUrls)).filter(
|
|
599
|
-
(url, _, urls) =>
|
|
600
|
-
!urls.some((otherUrl) => otherUrl !== url && otherUrl.startsWith(url))
|
|
601
|
-
);
|
|
602
|
-
|
|
603
|
-
dedupedDetectedUrls.forEach((url) => emitAuthUrl(url, false));
|
|
604
|
-
|
|
605
|
-
if (
|
|
606
|
-
shouldAutoOpenUrlFromOutput(cleanChunk) &&
|
|
607
|
-
dedupedDetectedUrls.length > 0
|
|
608
|
-
) {
|
|
609
|
-
const bestUrl = dedupedDetectedUrls.reduce((longest, current) =>
|
|
610
|
-
current.length > longest.length ? current : longest
|
|
611
|
-
);
|
|
612
|
-
emitAuthUrl(bestUrl, true);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
session.ws.send(
|
|
616
|
-
JSON.stringify({
|
|
617
|
-
type: 'output',
|
|
618
|
-
data: outputData,
|
|
619
|
-
})
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
shellProcess.onExit((exitCode) => {
|
|
625
|
-
if (!ptySessionKey) {
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
const session = ptySessionsMap.get(ptySessionKey);
|
|
630
|
-
if (session && session.pty !== shellProcess) {
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
if (session && session.ws && session.ws.readyState === WebSocket.OPEN) {
|
|
635
|
-
session.ws.send(
|
|
636
|
-
JSON.stringify({
|
|
637
|
-
type: 'output',
|
|
638
|
-
data: `\r\n\x1b[33mProcess exited with code ${exitCode.exitCode}${
|
|
639
|
-
exitCode.signal != null ? ` (${exitCode.signal})` : ''
|
|
640
|
-
}\x1b[0m\r\n`,
|
|
641
|
-
})
|
|
642
|
-
);
|
|
643
|
-
}
|
|
644
|
-
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (session.ws && session.ws.readyState === WebSocket.OPEN) {
|
|
566
|
+
let outputData = chunk;
|
|
567
|
+
const cleanChunk = stripAnsiSequences(chunk);
|
|
568
|
+
urlDetectionBuffer = `${urlDetectionBuffer}${cleanChunk}`.slice(-SHELL_URL_PARSE_BUFFER_LIMIT);
|
|
569
|
+
|
|
570
|
+
outputData = outputData.replace(
|
|
571
|
+
/OPEN_URL:\s*(https?:\/\/[^\s\x1b\x07]+)/g,
|
|
572
|
+
'[INFO] Opening in browser: $1'
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
const emitAuthUrl = (detectedUrl: string, autoOpen = false) => {
|
|
576
|
+
const normalizedUrl = normalizeDetectedUrl(detectedUrl);
|
|
577
|
+
if (!normalizedUrl) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const isNewUrl = !announcedAuthUrls.has(normalizedUrl);
|
|
582
|
+
if (isNewUrl) {
|
|
583
|
+
announcedAuthUrls.add(normalizedUrl);
|
|
584
|
+
session.ws?.send(
|
|
585
|
+
JSON.stringify({
|
|
586
|
+
type: 'auth_url',
|
|
587
|
+
url: normalizedUrl,
|
|
588
|
+
autoOpen,
|
|
589
|
+
})
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
const normalizedDetectedUrls = extractUrlsFromText(urlDetectionBuffer)
|
|
595
|
+
.map((url) => normalizeDetectedUrl(url))
|
|
596
|
+
.filter((url): url is string => Boolean(url));
|
|
597
|
+
|
|
598
|
+
const dedupedDetectedUrls = Array.from(new Set(normalizedDetectedUrls)).filter(
|
|
599
|
+
(url, _, urls) =>
|
|
600
|
+
!urls.some((otherUrl) => otherUrl !== url && otherUrl.startsWith(url))
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
dedupedDetectedUrls.forEach((url) => emitAuthUrl(url, false));
|
|
604
|
+
|
|
605
|
+
if (
|
|
606
|
+
shouldAutoOpenUrlFromOutput(cleanChunk) &&
|
|
607
|
+
dedupedDetectedUrls.length > 0
|
|
608
|
+
) {
|
|
609
|
+
const bestUrl = dedupedDetectedUrls.reduce((longest, current) =>
|
|
610
|
+
current.length > longest.length ? current : longest
|
|
611
|
+
);
|
|
612
|
+
emitAuthUrl(bestUrl, true);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
session.ws.send(
|
|
616
|
+
JSON.stringify({
|
|
617
|
+
type: 'output',
|
|
618
|
+
data: outputData,
|
|
619
|
+
})
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
shellProcess.onExit((exitCode) => {
|
|
625
|
+
if (!ptySessionKey) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const session = ptySessionsMap.get(ptySessionKey);
|
|
630
|
+
if (session && session.pty !== shellProcess) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (session && session.ws && session.ws.readyState === WebSocket.OPEN) {
|
|
635
|
+
session.ws.send(
|
|
636
|
+
JSON.stringify({
|
|
637
|
+
type: 'output',
|
|
638
|
+
data: `\r\n\x1b[33mProcess exited with code ${exitCode.exitCode}${
|
|
639
|
+
exitCode.signal != null ? ` (${exitCode.signal})` : ''
|
|
640
|
+
}\x1b[0m\r\n`,
|
|
641
|
+
})
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
645
|
if (session?.timeoutId) {
|
|
646
646
|
clearTimeout(session.timeoutId);
|
|
647
647
|
}
|
|
648
648
|
|
|
649
649
|
terminalReplayStore.delete(ptySessionKey);
|
|
650
650
|
ptySessionsMap.delete(ptySessionKey);
|
|
651
|
-
shellProcess = null;
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
let welcomeMsg = `\x1b[36mStarting terminal in: ${projectPath}\x1b[0m\r\n`;
|
|
655
|
-
if (!isPlainShell) {
|
|
656
|
-
const providerName =
|
|
657
|
-
provider === 'cursor'
|
|
658
|
-
? 'Cursor'
|
|
659
|
-
: provider === 'codex'
|
|
660
|
-
? 'Codex'
|
|
661
|
-
: provider === 'opencode'
|
|
662
|
-
? 'OpenCode'
|
|
663
|
-
: 'Claude';
|
|
664
|
-
welcomeMsg = hasSession && resumeSessionId
|
|
665
|
-
? `\x1b[36mResuming ${providerName} session ${resumeSessionId} in: ${projectPath}\x1b[0m\r\n`
|
|
666
|
-
: `\x1b[36mStarting new ${providerName} session in: ${projectPath}\x1b[0m\r\n`;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
ws.send(
|
|
670
|
-
JSON.stringify({
|
|
671
|
-
type: 'output',
|
|
672
|
-
data: welcomeMsg,
|
|
673
|
-
})
|
|
674
|
-
);
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
|
|
651
|
+
shellProcess = null;
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
let welcomeMsg = `\x1b[36mStarting terminal in: ${projectPath}\x1b[0m\r\n`;
|
|
655
|
+
if (!isPlainShell) {
|
|
656
|
+
const providerName =
|
|
657
|
+
provider === 'cursor'
|
|
658
|
+
? 'Cursor'
|
|
659
|
+
: provider === 'codex'
|
|
660
|
+
? 'Codex'
|
|
661
|
+
: provider === 'opencode'
|
|
662
|
+
? 'OpenCode'
|
|
663
|
+
: 'Claude';
|
|
664
|
+
welcomeMsg = hasSession && resumeSessionId
|
|
665
|
+
? `\x1b[36mResuming ${providerName} session ${resumeSessionId} in: ${projectPath}\x1b[0m\r\n`
|
|
666
|
+
: `\x1b[36mStarting new ${providerName} session in: ${projectPath}\x1b[0m\r\n`;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
ws.send(
|
|
670
|
+
JSON.stringify({
|
|
671
|
+
type: 'output',
|
|
672
|
+
data: welcomeMsg,
|
|
673
|
+
})
|
|
674
|
+
);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
678
|
if (data.type === 'input') {
|
|
679
679
|
const activeSession = ptySessionKey ? ptySessionsMap.get(ptySessionKey) : null;
|
|
680
680
|
if (shellProcess && activeSession?.pty === shellProcess && activeSession.ws === ws) {
|
|
@@ -723,54 +723,54 @@ export function handleShellConnection(
|
|
|
723
723
|
);
|
|
724
724
|
}
|
|
725
725
|
}
|
|
726
|
-
} catch (error) {
|
|
727
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
728
|
-
console.error('[ERROR] Shell WebSocket error:', message);
|
|
729
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
730
|
-
ws.send(
|
|
731
|
-
JSON.stringify({
|
|
732
|
-
type: 'output',
|
|
733
|
-
data: `\r\n\x1b[31mError: ${message}\x1b[0m\r\n`,
|
|
734
|
-
})
|
|
735
|
-
);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
ws.on('close', () => {
|
|
741
|
-
if (!ptySessionKey) {
|
|
742
|
-
return;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
const session = ptySessionsMap.get(ptySessionKey);
|
|
746
|
-
if (!session) {
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// Mobile networks can deliver an old socket's close after its replacement
|
|
751
|
-
// has attached. Only the socket that currently owns the PTY may detach it.
|
|
752
|
-
if (session.ws !== ws) {
|
|
753
|
-
return;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
session.ws = null;
|
|
757
|
-
if (session.timeoutId) {
|
|
758
|
-
clearTimeout(session.timeoutId);
|
|
759
|
-
}
|
|
760
|
-
session.timeoutId = setTimeout(() => {
|
|
761
|
-
// A reconnect may win just as this timer becomes runnable. Re-check the
|
|
762
|
-
// active socket so a queued cleanup can never kill a reattached PTY.
|
|
763
|
-
if (ptySessionsMap.get(ptySessionKey as string) !== session || session.ws !== null) {
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
|
|
726
|
+
} catch (error) {
|
|
727
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
728
|
+
console.error('[ERROR] Shell WebSocket error:', message);
|
|
729
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
730
|
+
ws.send(
|
|
731
|
+
JSON.stringify({
|
|
732
|
+
type: 'output',
|
|
733
|
+
data: `\r\n\x1b[31mError: ${message}\x1b[0m\r\n`,
|
|
734
|
+
})
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
ws.on('close', () => {
|
|
741
|
+
if (!ptySessionKey) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const session = ptySessionsMap.get(ptySessionKey);
|
|
746
|
+
if (!session) {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Mobile networks can deliver an old socket's close after its replacement
|
|
751
|
+
// has attached. Only the socket that currently owns the PTY may detach it.
|
|
752
|
+
if (session.ws !== ws) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
session.ws = null;
|
|
757
|
+
if (session.timeoutId) {
|
|
758
|
+
clearTimeout(session.timeoutId);
|
|
759
|
+
}
|
|
760
|
+
session.timeoutId = setTimeout(() => {
|
|
761
|
+
// A reconnect may win just as this timer becomes runnable. Re-check the
|
|
762
|
+
// active socket so a queued cleanup can never kill a reattached PTY.
|
|
763
|
+
if (ptySessionsMap.get(ptySessionKey as string) !== session || session.ws !== null) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
767
|
session.pty.kill();
|
|
768
768
|
terminalReplayStore.delete(ptySessionKey as string);
|
|
769
769
|
ptySessionsMap.delete(ptySessionKey as string);
|
|
770
|
-
}, PTY_SESSION_TIMEOUT);
|
|
771
|
-
});
|
|
772
|
-
|
|
773
|
-
ws.on('error', (error) => {
|
|
774
|
-
console.error('[ERROR] Shell WebSocket error:', error);
|
|
775
|
-
});
|
|
776
|
-
}
|
|
770
|
+
}, PTY_SESSION_TIMEOUT);
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
ws.on('error', (error) => {
|
|
774
|
+
console.error('[ERROR] Shell WebSocket error:', error);
|
|
775
|
+
});
|
|
776
|
+
}
|