hjworktree-cli 2.2.0 → 2.4.0

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.
Files changed (37) hide show
  1. package/.claude/settings.local.json +11 -2
  2. package/.context-snapshots/context-snapshot-20260106-211500.md +85 -0
  3. package/README.md +26 -10
  4. package/dist/server/socketHandlers.d.ts.map +1 -1
  5. package/dist/server/socketHandlers.js +58 -23
  6. package/dist/server/socketHandlers.js.map +1 -1
  7. package/dist/shared/constants.d.ts +1 -3
  8. package/dist/shared/constants.d.ts.map +1 -1
  9. package/dist/shared/constants.js +1 -24
  10. package/dist/shared/constants.js.map +1 -1
  11. package/dist/shared/types/index.d.ts +3 -15
  12. package/dist/shared/types/index.d.ts.map +1 -1
  13. package/dist/shared/types/index.js +1 -1
  14. package/dist/shared/types/index.js.map +1 -1
  15. package/dist/web/assets/index-D-hASqdI.js +53 -0
  16. package/dist/web/assets/index-D-hASqdI.js.map +1 -0
  17. package/dist/web/assets/index-Dgl6wRHk.css +32 -0
  18. package/dist/web/index.html +2 -2
  19. package/package.json +3 -2
  20. package/scripts/fix-pty-permissions.js +89 -0
  21. package/server/socketHandlers.ts +66 -28
  22. package/shared/constants.ts +1 -27
  23. package/shared/types/index.ts +6 -21
  24. package/web/src/App.tsx +8 -6
  25. package/web/src/components/Layout/LeftNavBar.tsx +6 -17
  26. package/web/src/components/Modals/AddWorktreeModal.tsx +1 -21
  27. package/web/src/components/Steps/WorktreeStep.tsx +1 -8
  28. package/web/src/components/Terminal/SplitTerminalView.tsx +64 -0
  29. package/web/src/components/Terminal/TerminalPanel.tsx +3 -69
  30. package/web/src/components/Terminal/XTerminal.tsx +4 -6
  31. package/web/src/stores/useAppStore.ts +77 -35
  32. package/web/src/styles/global.css +127 -77
  33. package/dist/web/assets/index-CsixHL-D.css +0 -32
  34. package/dist/web/assets/index-D8dr9mJa.js +0 -53
  35. package/dist/web/assets/index-D8dr9mJa.js.map +0 -1
  36. package/web/src/components/Setup/AgentSelector.tsx +0 -27
  37. package/web/src/components/Steps/AgentStep.tsx +0 -20
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Copyright (c) 2014 The xterm.js authors. All rights reserved.
3
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
4
+ * https://github.com/chjj/term.js
5
+ * @license MIT
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ *
25
+ * Originally forked from (with the author's permission):
26
+ * Fabrice Bellard's javascript vt100 for jslinux:
27
+ * http://bellard.org/jslinux/
28
+ * Copyright (c) 2011 Fabrice Bellard
29
+ * The original design remains. The terminal itself
30
+ * has been extended to include xterm CSI codes, among
31
+ * other features.
32
+ */.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}*{margin:0;padding:0;box-sizing:border-box}:root{--bg-primary: #0d1117;--bg-secondary: #161b22;--bg-tertiary: #21262d;--border-color: #30363d;--text-primary: #c9d1d9;--text-secondary: #8b949e;--text-muted: #6e7681;--accent-blue: #58a6ff;--accent-green: #3fb950;--accent-yellow: #d29922;--accent-red: #f85149;--accent-purple: #a371f7}html,body,#root{height:100%;width:100%}body{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background-color:var(--bg-primary);color:var(--text-primary);line-height:1.5}.app{display:flex;flex-direction:column;height:100vh;overflow:hidden}.header{display:flex;align-items:center;justify-content:space-between;padding:12px 24px;background-color:var(--bg-secondary);border-bottom:1px solid var(--border-color)}.header h1{font-size:18px;font-weight:600;color:var(--text-primary)}.header .project-info{display:flex;align-items:center;gap:12px;color:var(--text-secondary);font-size:13px}.header .project-info .branch{display:flex;align-items:center;gap:4px;color:var(--accent-green)}.main-content{flex:1;display:flex;flex-direction:column;overflow:hidden}.setup-panel{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:32px}.setup-panel h2{font-size:24px;font-weight:600;margin-bottom:8px}.setup-panel p{color:var(--text-secondary);font-size:14px}.setup-section{width:100%;max-width:500px;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;padding:20px}.setup-section label{display:block;font-size:14px;font-weight:500;margin-bottom:8px;color:var(--text-primary)}.setup-section select,.setup-section input{width:100%;padding:10px 12px;border:1px solid var(--border-color);border-radius:6px;background-color:var(--bg-tertiary);color:var(--text-primary);font-size:14px;font-family:inherit}.setup-section select:focus,.setup-section input:focus{outline:none;border-color:var(--accent-blue)}.setup-section .counter{display:flex;align-items:center;gap:16px}.setup-section .counter button{width:40px;height:40px;border:1px solid var(--border-color);border-radius:6px;background-color:var(--bg-tertiary);color:var(--text-primary);font-size:20px;cursor:pointer;transition:all .2s}.setup-section .counter button:hover{background-color:var(--bg-primary);border-color:var(--accent-blue)}.setup-section .counter button:disabled{opacity:.5;cursor:not-allowed}.setup-section .counter span{font-size:24px;font-weight:600;min-width:60px;text-align:center}.execute-button{padding:14px 48px;background-color:var(--accent-green);color:var(--bg-primary);border:none;border-radius:8px;font-size:16px;font-weight:600;cursor:pointer;transition:all .2s}.execute-button:hover{filter:brightness(1.1)}.execute-button:disabled{background-color:var(--bg-tertiary);color:var(--text-muted);cursor:not-allowed}.terminal-panel{display:flex;flex-direction:column;height:100%;overflow:hidden}.terminal-tabs{display:flex;align-items:center;gap:4px;padding:8px 16px;background-color:var(--bg-secondary);border-bottom:1px solid var(--border-color);overflow-x:auto}.terminal-tab{display:flex;align-items:center;gap:8px;padding:8px 16px;background-color:transparent;border:1px solid transparent;border-radius:6px;color:var(--text-secondary);font-size:13px;cursor:pointer;transition:all .2s;white-space:nowrap}.terminal-tab:hover{background-color:var(--bg-tertiary)}.terminal-tab.active{background-color:var(--bg-tertiary);border-color:var(--border-color);color:var(--text-primary)}.terminal-tab .status{width:8px;height:8px;border-radius:50%}.terminal-tab .status.running{background-color:var(--accent-green)}.terminal-tab .status.initializing{background-color:var(--accent-yellow)}.terminal-tab .status.stopped{background-color:var(--text-muted)}.terminal-tab .status.error{background-color:var(--accent-red)}.terminal-tab .close-btn{padding:2px 4px;background:transparent;border:none;color:var(--text-muted);cursor:pointer;border-radius:4px;line-height:1}.terminal-tab .close-btn:hover{background-color:var(--bg-primary);color:var(--accent-red)}.terminal-actions{display:flex;align-items:center;gap:8px;margin-left:auto;padding-left:16px}.terminal-actions button{padding:6px 12px;background-color:transparent;border:1px solid var(--border-color);border-radius:6px;color:var(--text-secondary);font-size:12px;cursor:pointer;transition:all .2s}.terminal-actions button:hover{background-color:var(--bg-tertiary);color:var(--text-primary)}.terminal-actions button.danger:hover{border-color:var(--accent-red);color:var(--accent-red)}.terminal-container{flex:1;position:relative;overflow:hidden}.terminal-wrapper{position:absolute;top:0;left:0;right:0;bottom:0;padding:8px}.terminal-wrapper.hidden{visibility:hidden}.terminal{height:100%;width:100%}.loading-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#0d1117e6;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;z-index:1000}.loading-spinner{width:48px;height:48px;border:3px solid var(--border-color);border-top-color:var(--accent-blue);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.loading-overlay p{color:var(--text-secondary);font-size:14px}.connection-status{position:fixed;bottom:16px;right:16px;display:flex;align-items:center;gap:8px;padding:8px 12px;background-color:var(--bg-secondary);border:1px solid var(--border-color);border-radius:6px;font-size:12px;color:var(--text-secondary)}.connection-status .dot{width:8px;height:8px;border-radius:50%}.connection-status .dot.connected{background-color:var(--accent-green)}.connection-status .dot.disconnected{background-color:var(--accent-red)}.error-banner{padding:12px 16px;background-color:#f851491a;border:1px solid var(--accent-red);border-radius:6px;color:var(--accent-red);font-size:14px;text-align:center}.main-layout{display:flex;flex:1;overflow:hidden}.main-content-area{flex:1;display:flex;flex-direction:column;overflow:hidden}.main-content-area.with-lnb{margin-left:0}.main-content-area.full-width{width:100%}.left-nav-bar{width:260px;background-color:var(--bg-secondary);border-right:1px solid var(--border-color);display:flex;flex-direction:column;flex-shrink:0}.lnb-header{padding:20px;border-bottom:1px solid var(--border-color);font-size:14px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px}.lnb-steps{padding:16px;display:flex;flex-direction:column;gap:8px}.lnb-step{display:flex;align-items:center;gap:12px;padding:14px 16px;background:transparent;border:1px solid transparent;border-radius:8px;text-align:left;cursor:pointer;transition:all .2s ease;width:100%;font-family:inherit}.lnb-step:hover:not(:disabled){background-color:var(--bg-tertiary)}.lnb-step:disabled{cursor:not-allowed;opacity:.5}.step-number{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:600;flex-shrink:0;transition:all .2s ease}.lnb-step.pending .step-number{background-color:var(--bg-tertiary);color:var(--text-muted);border:2px solid var(--border-color)}.lnb-step.pending .step-label{color:var(--text-muted)}.lnb-step.current{background-color:#58a6ff1a;border-color:var(--accent-blue)}.lnb-step.current .step-number{background-color:var(--accent-blue);color:var(--bg-primary)}.lnb-step.current .step-label{color:var(--text-primary);font-weight:500}.lnb-step.completed .step-number{background-color:var(--accent-green);color:var(--bg-primary)}.lnb-step.completed .step-label{color:var(--text-primary)}.step-label{font-size:14px;flex:1}.step-container{flex:1;display:flex;flex-direction:column;padding:40px;max-width:600px;margin:0 auto;width:100%}.step-header{text-align:center;margin-bottom:32px}.step-header h2{font-size:24px;font-weight:600;margin-bottom:8px;color:var(--text-primary)}.step-header p{color:var(--text-secondary);font-size:14px}.step-content{flex:1;display:flex;flex-direction:column;gap:24px}.step-navigation{display:flex;justify-content:space-between;padding-top:32px;margin-top:auto;border-top:1px solid var(--border-color)}.nav-button{padding:12px 32px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;font-family:inherit}.nav-button.prev{background:transparent;border:1px solid var(--border-color);color:var(--text-secondary)}.nav-button.prev:hover:not(:disabled){background-color:var(--bg-tertiary);color:var(--text-primary)}.nav-button.prev:disabled{opacity:.3;cursor:not-allowed}.nav-button.next{background-color:var(--accent-blue);border:none;color:#fff}.nav-button.next:hover:not(:disabled){filter:brightness(1.1)}.nav-button.next:disabled{background-color:var(--bg-tertiary);color:var(--text-muted);cursor:not-allowed}.execution-summary{background-color:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;padding:20px}.execution-summary h4{font-size:14px;font-weight:600;margin-bottom:16px;color:var(--text-primary);padding-bottom:12px;border-bottom:1px solid var(--border-color)}.summary-item{display:flex;justify-content:space-between;align-items:center;padding:8px 0}.summary-label{font-size:13px;color:var(--text-secondary)}.summary-value{font-size:13px;font-weight:500;color:var(--text-primary)}.left-nav-bar{width:280px;background-color:var(--bg-secondary);border-right:1px solid var(--border-color);display:flex;flex-direction:column;flex-shrink:0;overflow-y:auto}.lnb-section{border-bottom:1px solid var(--border-color)}.lnb-section-header{display:flex;align-items:center;justify-content:space-between;padding:16px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .2s}.lnb-section-header:hover{background-color:var(--bg-tertiary)}.section-title{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px}.collapse-icon{font-size:14px;color:var(--text-muted)}.sessions-section{flex:1;display:flex;flex-direction:column;min-height:0}.session-count{font-size:11px;background-color:var(--bg-tertiary);color:var(--text-secondary);padding:2px 8px;border-radius:10px;margin-left:8px}.session-list{flex:1;overflow-y:auto;padding:8px}.session-group{margin-bottom:8px}.session-group-header{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;border-radius:6px;transition:background-color .2s}.session-group-header:hover{background-color:var(--bg-tertiary)}.group-collapse-icon{font-size:10px;color:var(--text-muted)}.group-name{font-size:12px;font-weight:500;color:var(--text-secondary);flex:1}.group-count{font-size:11px;color:var(--text-muted)}.session-group-items{padding-left:12px}.session-item{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;border-radius:6px;margin:2px 0;border:1px solid transparent;transition:all .2s}.session-item:hover{background-color:var(--bg-tertiary)}.session-item.active{background-color:#58a6ff1a;border-color:var(--accent-blue)}.session-item.selected{background-color:#58a6ff0d}.session-checkbox{width:14px;height:14px;cursor:pointer;accent-color:var(--accent-blue)}.status-indicator{width:8px;height:8px;border-radius:50%;flex-shrink:0}.session-name{flex:1;font-size:12px;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.session-close{padding:2px 6px;background:transparent;border:none;color:var(--text-muted);cursor:pointer;border-radius:4px;font-size:14px;line-height:1;opacity:0;transition:all .2s}.session-item:hover .session-close{opacity:1}.session-close:hover{background-color:var(--bg-primary);color:var(--accent-red)}.add-session-btn{margin:8px 16px 16px;padding:10px;background-color:transparent;border:1px dashed var(--border-color);border-radius:6px;color:var(--text-secondary);font-size:12px;cursor:pointer;transition:all .2s}.add-session-btn:hover{border-color:var(--accent-blue);color:var(--accent-blue);background-color:#58a6ff0d}.bulk-actions-bar{display:flex;align-items:center;gap:12px;padding:12px 16px;background-color:var(--bg-tertiary);border-top:1px solid var(--border-color)}.selected-count{font-size:12px;color:var(--text-secondary);flex:1}.bulk-delete-btn{padding:6px 12px;background-color:var(--accent-red);border:none;border-radius:4px;color:#fff;font-size:11px;font-weight:500;cursor:pointer;transition:filter .2s}.bulk-delete-btn:hover{filter:brightness(1.1)}.bulk-cancel-btn{padding:6px 12px;background-color:transparent;border:1px solid var(--border-color);border-radius:4px;color:var(--text-secondary);font-size:11px;cursor:pointer;transition:all .2s}.bulk-cancel-btn:hover{background-color:var(--bg-primary);color:var(--text-primary)}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#000000b3;display:flex;align-items:center;justify-content:center;z-index:1000}.modal-content{background-color:var(--bg-secondary);border:1px solid var(--border-color);border-radius:12px;width:100%;max-width:440px;max-height:90vh;overflow:hidden;display:flex;flex-direction:column}.modal-confirm{max-width:400px}.modal-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid var(--border-color)}.modal-header h2{font-size:16px;font-weight:600;color:var(--text-primary)}.modal-close{padding:4px 8px;background:transparent;border:none;color:var(--text-muted);font-size:20px;cursor:pointer;border-radius:4px;line-height:1}.modal-close:hover{background-color:var(--bg-tertiary);color:var(--text-primary)}.modal-body{padding:24px;overflow-y:auto}.modal-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid var(--border-color)}.form-group{margin-bottom:20px}.form-group:last-child{margin-bottom:0}.form-group label{display:block;font-size:13px;font-weight:500;color:var(--text-primary);margin-bottom:8px}.form-group select{width:100%;padding:10px 12px;background-color:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:6px;color:var(--text-primary);font-size:14px;font-family:inherit}.form-group select:focus{outline:none;border-color:var(--accent-blue)}.btn-primary{padding:10px 20px;background-color:var(--accent-blue);border:none;border-radius:6px;color:#fff;font-size:13px;font-weight:500;cursor:pointer;transition:filter .2s;font-family:inherit}.btn-primary:hover:not(:disabled){filter:brightness(1.1)}.btn-primary:disabled{background-color:var(--bg-tertiary);color:var(--text-muted);cursor:not-allowed}.btn-secondary{padding:10px 20px;background-color:transparent;border:1px solid var(--border-color);border-radius:6px;color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;transition:all .2s;font-family:inherit}.btn-secondary:hover:not(:disabled){background-color:var(--bg-tertiary);color:var(--text-primary)}.btn-danger{padding:10px 20px;background-color:var(--accent-red);border:none;border-radius:6px;color:#fff;font-size:13px;font-weight:500;cursor:pointer;transition:filter .2s;font-family:inherit}.btn-danger:hover:not(:disabled){filter:brightness(1.1)}.btn-danger:disabled{opacity:.5;cursor:not-allowed}.confirm-message{font-size:14px;color:var(--text-primary);margin-bottom:16px}.delete-list{list-style:none;padding:12px;background-color:var(--bg-tertiary);border-radius:6px;margin-bottom:16px;max-height:150px;overflow-y:auto}.delete-list li{display:flex;align-items:center;gap:8px;padding:6px 0;font-size:13px;color:var(--text-secondary)}.delete-list .status{width:6px;height:6px;border-radius:50%}.warning-text{font-size:12px;color:var(--accent-yellow);padding:12px;background-color:#d299221a;border-radius:6px}.terminal-panel.empty{display:flex;align-items:center;justify-content:center}.empty-message{text-align:center;color:var(--text-muted)}.empty-message p{margin:8px 0}.empty-message p:first-child{font-size:16px;color:var(--text-secondary)}.terminal-header{display:flex;align-items:center;gap:12px;padding:8px 16px;background-color:var(--bg-secondary);border-bottom:1px solid var(--border-color)}.terminal-grid-container{flex:1;display:flex;flex-direction:column;overflow:hidden;padding:8px;background-color:var(--bg-primary)}.terminal-grid-container.scrollable{overflow-y:auto}.terminal-grid{flex:1;display:grid;gap:8px;min-height:0}.terminal-grid.cols-2{grid-template-columns:repeat(2,1fr)}.terminal-grid-container.scrollable .terminal-grid{grid-auto-rows:minmax(350px,1fr)}.terminal-pane{position:relative;display:flex;flex-direction:column;border:2px solid var(--border-color);border-radius:8px;overflow:hidden;background-color:var(--bg-primary);min-height:300px;transition:border-color .2s,box-shadow .2s}.terminal-pane:hover{border-color:var(--text-muted)}.terminal-pane.active{border-color:var(--accent-green)}.pane-header{display:flex;align-items:center;gap:8px;padding:6px 10px;background-color:var(--bg-secondary);border-bottom:1px solid var(--border-color);min-height:32px}.pane-status{width:8px;height:8px;border-radius:50%;flex-shrink:0}.pane-status.running{background-color:var(--accent-green)}.pane-status.initializing{background-color:var(--accent-yellow)}.pane-status.stopped{background-color:var(--text-muted)}.pane-status.error{background-color:var(--accent-red)}.pane-title{flex:1;font-size:11px;font-weight:500;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pane-terminal{flex:1;position:relative;overflow:hidden}.pane-terminal .terminal{height:100%;width:100%;padding:4px}.empty-grid-message{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text-muted);gap:16px}.empty-grid-message p{font-size:14px}
@@ -7,8 +7,8 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
10
- <script type="module" crossorigin src="/assets/index-D8dr9mJa.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-CsixHL-D.css">
10
+ <script type="module" crossorigin src="/assets/index-D-hASqdI.js"></script>
11
+ <link rel="stylesheet" crossorigin href="/assets/index-Dgl6wRHk.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hjworktree-cli",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Web-based git worktree parallel AI coding agent runner",
5
5
  "type": "module",
6
6
  "main": "dist/server/index.js",
@@ -15,6 +15,7 @@
15
15
  "build:server": "tsc",
16
16
  "build:web": "vite build --config web/vite.config.ts",
17
17
  "start": "node dist/server/index.js",
18
+ "postinstall": "node scripts/fix-pty-permissions.js",
18
19
  "prepublishOnly": "npm run build"
19
20
  },
20
21
  "keywords": [
@@ -37,7 +38,7 @@
37
38
  "dependencies": {
38
39
  "cors": "^2.8.5",
39
40
  "express": "^4.21.0",
40
- "node-pty": "^1.0.0",
41
+ "node-pty": "^1.1.0",
41
42
  "open": "^10.1.0",
42
43
  "simple-git": "^3.27.0",
43
44
  "socket.io": "^4.7.5"
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Fix node-pty spawn-helper permissions on macOS/Linux
5
+ *
6
+ * This script addresses the posix_spawnp failed error that occurs when
7
+ * spawn-helper lacks execute permissions after npm install.
8
+ *
9
+ * Reference: https://github.com/microsoft/node-pty/issues/670
10
+ */
11
+
12
+ import { chmod, access, constants } from 'fs';
13
+ import { resolve, dirname } from 'path';
14
+ import { fileURLToPath } from 'url';
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+
18
+ // Skip on Windows
19
+ if (process.platform === 'win32') {
20
+ process.exit(0);
21
+ }
22
+
23
+ // Possible spawn-helper locations
24
+ const possiblePaths = [
25
+ // When installed as dependency
26
+ resolve(__dirname, '../node_modules/node-pty/build/Release/spawn-helper'),
27
+ // When this package is installed globally
28
+ resolve(__dirname, '../node_modules/.pnpm/node-pty@1.1.0/node_modules/node-pty/build/Release/spawn-helper'),
29
+ // Direct prebuild location
30
+ resolve(__dirname, '../node_modules/node-pty/prebuilds'),
31
+ ];
32
+
33
+ // Find and fix spawn-helper permissions
34
+ async function fixPermissions() {
35
+ for (const spawnHelperPath of possiblePaths) {
36
+ try {
37
+ await new Promise((resolve, reject) => {
38
+ access(spawnHelperPath, constants.F_OK, (err) => {
39
+ if (err) reject(err);
40
+ else resolve();
41
+ });
42
+ });
43
+
44
+ // File exists, set execute permission
45
+ await new Promise((resolve, reject) => {
46
+ chmod(spawnHelperPath, 0o755, (err) => {
47
+ if (err) reject(err);
48
+ else resolve();
49
+ });
50
+ });
51
+
52
+ console.log(`[fix-pty-permissions] Fixed: ${spawnHelperPath}`);
53
+ } catch {
54
+ // File doesn't exist at this path, try next
55
+ }
56
+ }
57
+
58
+ // Also try to find spawn-helper recursively in node_modules
59
+ try {
60
+ const { execSync } = await import('child_process');
61
+ const result = execSync(
62
+ 'find node_modules -name "spawn-helper" -type f 2>/dev/null || true',
63
+ { cwd: resolve(__dirname, '..'), encoding: 'utf-8' }
64
+ );
65
+
66
+ const files = result.trim().split('\n').filter(Boolean);
67
+ for (const file of files) {
68
+ try {
69
+ const fullPath = resolve(__dirname, '..', file);
70
+ await new Promise((resolve, reject) => {
71
+ chmod(fullPath, 0o755, (err) => {
72
+ if (err) reject(err);
73
+ else resolve();
74
+ });
75
+ });
76
+ console.log(`[fix-pty-permissions] Fixed: ${fullPath}`);
77
+ } catch {
78
+ // Ignore errors for individual files
79
+ }
80
+ }
81
+ } catch {
82
+ // find command failed, ignore
83
+ }
84
+ }
85
+
86
+ fixPermissions().catch(() => {
87
+ // Don't fail the install if this script fails
88
+ process.exit(0);
89
+ });
@@ -6,14 +6,33 @@ import type {
6
6
  TerminalInputData,
7
7
  TerminalResizeData,
8
8
  TerminalKillData,
9
- AgentId
10
9
  } from '../shared/types/index.js';
11
- import { AI_AGENTS } from '../shared/constants.js';
12
- import fs from 'fs';
10
+ import fs, { realpathSync } from 'fs';
11
+ import path from 'path';
13
12
  import { promisify } from 'util';
14
13
 
15
14
  const access = promisify(fs.access);
16
15
 
16
+ // Environment and path utilities for macOS compatibility
17
+ function sanitizeEnv(env: NodeJS.ProcessEnv): Record<string, string> {
18
+ const sanitized: Record<string, string> = {};
19
+ for (const [key, value] of Object.entries(env)) {
20
+ if (typeof value === 'string' && value.length > 0) {
21
+ sanitized[key] = value;
22
+ }
23
+ }
24
+ return sanitized;
25
+ }
26
+
27
+ function normalizePath(targetPath: string): string {
28
+ try {
29
+ const absolutePath = path.resolve(targetPath);
30
+ return realpathSync(absolutePath);
31
+ } catch {
32
+ return path.resolve(targetPath);
33
+ }
34
+ }
35
+
17
36
  // Validation utilities
18
37
  async function validatePath(pathToCheck: string): Promise<{ valid: boolean; error?: string }> {
19
38
  try {
@@ -87,19 +106,26 @@ async function waitForSpawnInterval(): Promise<void> {
87
106
  interface TerminalSession {
88
107
  pty: IPty;
89
108
  worktreePath: string;
90
- agentType: AgentId;
91
- socketId: string;
109
+ socketId: string | null; // null when disconnected but session alive
92
110
  }
93
111
 
94
112
  const sessions = new Map<string, TerminalSession>();
95
113
 
96
- function getAgentCommand(agentType: AgentId): string {
97
- const agent = AI_AGENTS.find(a => a.id === agentType);
98
- return agent?.command || 'bash';
99
- }
100
-
101
114
  function getShell(): string {
102
- return process.platform === 'win32' ? 'powershell.exe' : process.env.SHELL || '/bin/bash';
115
+ if (process.platform === 'win32') {
116
+ return 'powershell.exe';
117
+ }
118
+
119
+ const shellPath = process.env.SHELL || '/bin/bash';
120
+
121
+ try {
122
+ const resolvedShell = realpathSync(shellPath);
123
+ fs.accessSync(resolvedShell, fs.constants.X_OK);
124
+ return resolvedShell;
125
+ } catch {
126
+ // Fallback: use shell name only (let PATH resolve it)
127
+ return path.basename(shellPath);
128
+ }
103
129
  }
104
130
 
105
131
  export function setupSocketHandlers(io: Server, cwd: string) {
@@ -108,13 +134,29 @@ export function setupSocketHandlers(io: Server, cwd: string) {
108
134
 
109
135
  // Terminal create
110
136
  socket.on('terminal:create', async (data: TerminalCreateData) => {
111
- const { sessionId, worktreePath, agentType } = data;
137
+ const { sessionId, worktreePath } = data;
138
+
139
+ console.log(`Creating terminal session: ${sessionId} at ${worktreePath}`);
140
+
141
+ // Check if session already exists (reattach scenario)
142
+ const existingSession = sessions.get(sessionId);
143
+ if (existingSession && existingSession.pty) {
144
+ console.log(`Reattaching to existing session: ${sessionId}`);
145
+
146
+ // Update socketId for the existing session
147
+ existingSession.socketId = socket.id;
148
+
149
+ // Re-register output handler for new socket
150
+ existingSession.pty.onData((output: string) => {
151
+ socket.emit('terminal:output', { sessionId, data: output });
152
+ });
112
153
 
113
- console.log(`Creating terminal session: ${sessionId} at ${worktreePath} with ${agentType}`);
154
+ socket.emit('terminal:created', { sessionId, reattached: true });
155
+ return;
156
+ }
114
157
 
115
158
  try {
116
159
  const shell = getShell();
117
- const agentCommand = getAgentCommand(agentType);
118
160
 
119
161
  // Validate path before spawning
120
162
  const pathValidation = await validatePath(worktreePath);
@@ -131,23 +173,25 @@ export function setupSocketHandlers(io: Server, cwd: string) {
131
173
  // Wait for spawn interval to prevent resource contention
132
174
  await waitForSpawnInterval();
133
175
 
176
+ // Normalize cwd path (resolve symlinks like /tmp -> /private/tmp on macOS)
177
+ const normalizedCwd = normalizePath(worktreePath);
178
+
134
179
  const ptyProcess = await spawnWithRetry(shell, [], {
135
180
  name: 'xterm-256color',
136
181
  cols: 120,
137
182
  rows: 30,
138
- cwd: worktreePath,
139
- env: {
183
+ cwd: normalizedCwd,
184
+ env: sanitizeEnv({
140
185
  ...process.env,
141
186
  TERM: 'xterm-256color',
142
187
  FORCE_COLOR: '1',
143
188
  COLORTERM: 'truecolor',
144
- } as Record<string, string>,
189
+ }),
145
190
  });
146
191
 
147
192
  sessions.set(sessionId, {
148
193
  pty: ptyProcess,
149
194
  worktreePath,
150
- agentType,
151
195
  socketId: socket.id,
152
196
  });
153
197
 
@@ -163,11 +207,6 @@ export function setupSocketHandlers(io: Server, cwd: string) {
163
207
  sessions.delete(sessionId);
164
208
  });
165
209
 
166
- // Start the AI agent after a short delay
167
- setTimeout(() => {
168
- ptyProcess.write(`${agentCommand}\r`);
169
- }, 500);
170
-
171
210
  socket.emit('terminal:created', { sessionId });
172
211
  } catch (error) {
173
212
  console.error(`Failed to create terminal session: ${sessionId}`, error);
@@ -215,7 +254,7 @@ export function setupSocketHandlers(io: Server, cwd: string) {
215
254
  const socketSessions = Array.from(sessions.entries())
216
255
  .filter(([, session]) => session.socketId === socket.id);
217
256
 
218
- for (const [sessionId, session] of socketSessions) {
257
+ for (const [, session] of socketSessions) {
219
258
  session.pty.write(data.data);
220
259
  }
221
260
  });
@@ -224,14 +263,13 @@ export function setupSocketHandlers(io: Server, cwd: string) {
224
263
  socket.on('disconnect', () => {
225
264
  console.log(`Client disconnected: ${socket.id}`);
226
265
 
227
- // Kill all sessions owned by this socket
266
+ // Keep sessions alive but mark socketId as null for potential reattach
228
267
  const socketSessions = Array.from(sessions.entries())
229
268
  .filter(([, session]) => session.socketId === socket.id);
230
269
 
231
270
  for (const [sessionId, session] of socketSessions) {
232
- console.log(`Killing orphaned terminal session: ${sessionId}`);
233
- session.pty.kill();
234
- sessions.delete(sessionId);
271
+ console.log(`Detaching terminal session (keeping alive): ${sessionId}`);
272
+ session.socketId = null;
235
273
  }
236
274
  });
237
275
  });
@@ -1,29 +1,3 @@
1
- import type { AgentType } from './types/index.js';
2
-
3
- export const AI_AGENTS: AgentType[] = [
4
- {
5
- id: 'codex',
6
- name: 'Codex CLI',
7
- command: 'codex',
8
- installCommand: 'npm install -g @openai/codex',
9
- description: 'OpenAI Codex CLI - AI-powered coding assistant'
10
- },
11
- {
12
- id: 'claude',
13
- name: 'Claude Code',
14
- command: 'claude',
15
- installCommand: 'npm install -g @anthropic-ai/claude-code',
16
- description: 'Anthropic Claude Code - Advanced AI coding assistant'
17
- },
18
- {
19
- id: 'gemini',
20
- name: 'Gemini CLI',
21
- command: 'gemini',
22
- installCommand: 'npm install -g @google/gemini-cli',
23
- description: 'Google Gemini CLI - Multi-modal AI assistant'
24
- }
25
- ];
26
-
27
1
  export const MAX_PARALLEL_COUNT = 10;
28
2
  export const MIN_PARALLEL_COUNT = 1;
29
3
  export const DEFAULT_PARALLEL_COUNT = 3;
@@ -31,5 +5,5 @@ export const DEFAULT_PARALLEL_COUNT = 3;
31
5
  export const BRANCH_POLL_INTERVAL = 5000; // 5 seconds
32
6
 
33
7
  export const APP_NAME = 'hjWorktree CLI';
34
- export const APP_VERSION = '2.0.0';
8
+ export const APP_VERSION = '2.4.0';
35
9
  export const DEFAULT_PORT = 3847;
@@ -1,5 +1,5 @@
1
- // Navigation step types (4-step wizard)
2
- export type NavigationStep = 'branch' | 'agent' | 'worktree' | 'running';
1
+ // Navigation step types (3-step wizard)
2
+ export type NavigationStep = 'branch' | 'worktree' | 'running';
3
3
 
4
4
  // Step status for LNB display
5
5
  export type StepStatus = 'pending' | 'current' | 'completed';
@@ -13,18 +13,7 @@ export interface StepConfig {
13
13
  }
14
14
 
15
15
  // Step order constant
16
- export const STEP_ORDER: NavigationStep[] = ['branch', 'agent', 'worktree', 'running'];
17
-
18
- // Agent types
19
- export type AgentId = 'codex' | 'claude' | 'gemini';
20
-
21
- export interface AgentType {
22
- id: AgentId;
23
- name: string;
24
- command: string;
25
- installCommand: string;
26
- description: string;
27
- }
16
+ export const STEP_ORDER: NavigationStep[] = ['branch', 'worktree', 'running'];
28
17
 
29
18
  // Branch types
30
19
  export interface Branch {
@@ -34,16 +23,15 @@ export interface Branch {
34
23
  lastCommit?: string;
35
24
  }
36
25
 
37
- // Running agent types
38
- export type AgentStatus = 'initializing' | 'installing' | 'running' | 'stopped' | 'error';
26
+ // Terminal status types
27
+ export type TerminalStatus = 'initializing' | 'running' | 'stopped' | 'error';
39
28
 
40
29
  export interface TerminalInfo {
41
30
  sessionId: string;
42
31
  worktreePath: string;
43
32
  worktreeName: string;
44
33
  branchName: string;
45
- agentType: AgentId;
46
- status: AgentStatus;
34
+ status: TerminalStatus;
47
35
  }
48
36
 
49
37
  // Worktree types
@@ -58,7 +46,6 @@ export interface Worktree {
58
46
  export interface TerminalCreateData {
59
47
  sessionId: string;
60
48
  worktreePath: string;
61
- agentType: AgentId;
62
49
  }
63
50
 
64
51
  export interface TerminalOutputData {
@@ -85,7 +72,6 @@ export interface TerminalKillData {
85
72
  export interface CreateWorktreesRequest {
86
73
  branch: string;
87
74
  count: number;
88
- agentType: AgentId;
89
75
  }
90
76
 
91
77
  export interface CreateWorktreesResponse {
@@ -95,7 +81,6 @@ export interface CreateWorktreesResponse {
95
81
  // Single worktree creation
96
82
  export interface CreateSingleWorktreeRequest {
97
83
  branch: string;
98
- agentType: AgentId;
99
84
  }
100
85
 
101
86
  export interface CreateSingleWorktreeResponse {
package/web/src/App.tsx CHANGED
@@ -4,7 +4,6 @@ import { useSocket } from './hooks/useSocket.js';
4
4
  import Header from './components/Layout/Header.js';
5
5
  import MainLayout from './components/Layout/MainLayout.js';
6
6
  import BranchStep from './components/Steps/BranchStep.js';
7
- import AgentStep from './components/Steps/AgentStep.js';
8
7
  import WorktreeStep from './components/Steps/WorktreeStep.js';
9
8
  import TerminalPanel from './components/Terminal/TerminalPanel.js';
10
9
  import ModalContainer from './components/Modals/ModalContainer.js';
@@ -13,18 +12,21 @@ function App() {
13
12
  const { step, isLoading, loadingMessage, error } = useAppStore();
14
13
  const { connected } = useSocket();
15
14
 
16
- // Fetch initial data
15
+ // Fetch initial data and restore existing sessions
17
16
  useEffect(() => {
18
- useAppStore.getState().fetchProjectInfo();
19
- useAppStore.getState().fetchBranches();
17
+ const initApp = async () => {
18
+ await useAppStore.getState().fetchProjectInfo();
19
+ await useAppStore.getState().fetchBranches();
20
+ // Restore existing worktrees if any (for page refresh persistence)
21
+ await useAppStore.getState().restoreExistingWorktrees();
22
+ };
23
+ initApp();
20
24
  }, []);
21
25
 
22
26
  const renderStepContent = () => {
23
27
  switch (step) {
24
28
  case 'branch':
25
29
  return <BranchStep />;
26
- case 'agent':
27
- return <AgentStep />;
28
30
  case 'worktree':
29
31
  return <WorktreeStep />;
30
32
  case 'running':
@@ -1,13 +1,11 @@
1
1
  import React from 'react';
2
2
  import { useAppStore } from '../../stores/useAppStore.js';
3
- import type { NavigationStep, StepStatus, AgentStatus } from '../../../../shared/types/index.js';
4
- import { AI_AGENTS } from '../../../../shared/constants.js';
3
+ import type { NavigationStep, StepStatus, TerminalStatus } from '../../../../shared/types/index.js';
5
4
 
6
5
  // Step configuration
7
6
  const STEP_CONFIG = [
8
7
  { id: 'branch' as const, number: 1, label: 'Branch' },
9
- { id: 'agent' as const, number: 2, label: 'Agent' },
10
- { id: 'worktree' as const, number: 3, label: 'Count' },
8
+ { id: 'worktree' as const, number: 2, label: 'Count' },
11
9
  ];
12
10
 
13
11
  interface StepItemProps {
@@ -79,7 +77,7 @@ function SetupSection() {
79
77
  onClick={() => goToStep('running')}
80
78
  type="button"
81
79
  >
82
- <span className="step-number">4</span>
80
+ <span className="step-number">3</span>
83
81
  <span className="step-label">Running</span>
84
82
  </button>
85
83
  )}
@@ -90,10 +88,9 @@ function SetupSection() {
90
88
  }
91
89
 
92
90
  // Status indicator component
93
- function StatusIndicator({ status }: { status: AgentStatus }) {
94
- const statusColors: Record<AgentStatus, string> = {
91
+ function StatusIndicator({ status }: { status: TerminalStatus }) {
92
+ const statusColors: Record<TerminalStatus, string> = {
95
93
  initializing: '#f59e0b',
96
- installing: '#f59e0b',
97
94
  running: '#22c55e',
98
95
  stopped: '#6b7280',
99
96
  error: '#ef4444',
@@ -112,8 +109,7 @@ function StatusIndicator({ status }: { status: AgentStatus }) {
112
109
  interface SessionItemProps {
113
110
  sessionId: string;
114
111
  worktreeName: string;
115
- agentType: string;
116
- status: AgentStatus;
112
+ status: TerminalStatus;
117
113
  isActive: boolean;
118
114
  isSelected: boolean;
119
115
  onSelect: () => void;
@@ -124,7 +120,6 @@ interface SessionItemProps {
124
120
  function SessionItem({
125
121
  sessionId,
126
122
  worktreeName,
127
- agentType,
128
123
  status,
129
124
  isActive,
130
125
  isSelected,
@@ -132,8 +127,6 @@ function SessionItem({
132
127
  onToggleSelect,
133
128
  onClose,
134
129
  }: SessionItemProps) {
135
- const agent = AI_AGENTS.find(a => a.id === agentType);
136
-
137
130
  return (
138
131
  <div
139
132
  className={`session-item ${isActive ? 'active' : ''} ${isSelected ? 'selected' : ''}`}
@@ -152,9 +145,6 @@ function SessionItem({
152
145
  <span className="session-name" title={worktreeName}>
153
146
  {worktreeName}
154
147
  </span>
155
- <span className="session-agent" title={agent?.name}>
156
- {agentType.charAt(0).toUpperCase()}
157
- </span>
158
148
  <button
159
149
  className="session-close"
160
150
  onClick={onClose}
@@ -256,7 +246,6 @@ function SessionsSection() {
256
246
  key={session.sessionId}
257
247
  sessionId={session.sessionId}
258
248
  worktreeName={session.worktreeName}
259
- agentType={session.agentType}
260
249
  status={session.status}
261
250
  isActive={session.sessionId === activeSessionId}
262
251
  isSelected={selectedSessionIds.includes(session.sessionId)}
@@ -1,16 +1,13 @@
1
1
  import React, { useState } from 'react';
2
2
  import { useAppStore } from '../../stores/useAppStore.js';
3
- import { AI_AGENTS } from '../../../../shared/constants.js';
4
- import type { AgentId } from '../../../../shared/types/index.js';
5
3
 
6
4
  function AddWorktreeModal() {
7
5
  const { branches, closeModal, createSingleWorktree, isLoading } = useAppStore();
8
6
  const [selectedBranch, setSelectedBranch] = useState<string>('');
9
- const [selectedAgent, setSelectedAgent] = useState<AgentId>('claude');
10
7
 
11
8
  const handleCreate = async () => {
12
9
  if (!selectedBranch) return;
13
- await createSingleWorktree(selectedBranch, selectedAgent);
10
+ await createSingleWorktree(selectedBranch);
14
11
  };
15
12
 
16
13
  return (
@@ -42,23 +39,6 @@ function AddWorktreeModal() {
42
39
  ))}
43
40
  </select>
44
41
  </div>
45
-
46
- <div className="form-group">
47
- <label>Agent</label>
48
- <div className="agent-options">
49
- {AI_AGENTS.map((agent) => (
50
- <button
51
- key={agent.id}
52
- type="button"
53
- className={`agent-option ${selectedAgent === agent.id ? 'selected' : ''}`}
54
- onClick={() => setSelectedAgent(agent.id)}
55
- disabled={isLoading}
56
- >
57
- <span className="agent-name">{agent.name}</span>
58
- </button>
59
- ))}
60
- </div>
61
- </div>
62
42
  </div>
63
43
 
64
44
  <div className="modal-footer">
@@ -2,12 +2,9 @@ import React from 'react';
2
2
  import StepContainer from '../Layout/StepContainer.js';
3
3
  import WorktreeCountSelector from '../Setup/WorktreeCountSelector.js';
4
4
  import { useAppStore } from '../../stores/useAppStore.js';
5
- import { AI_AGENTS } from '../../../../shared/constants.js';
6
5
 
7
6
  function WorktreeStep() {
8
- const { execute, isLoading, selectedBranch, selectedAgent, worktreeCount } = useAppStore();
9
-
10
- const agentName = AI_AGENTS.find(a => a.id === selectedAgent)?.name || selectedAgent;
7
+ const { execute, isLoading, selectedBranch, worktreeCount } = useAppStore();
11
8
 
12
9
  return (
13
10
  <StepContainer
@@ -25,10 +22,6 @@ function WorktreeStep() {
25
22
  <span className="summary-label">Branch:</span>
26
23
  <span className="summary-value">{selectedBranch}</span>
27
24
  </div>
28
- <div className="summary-item">
29
- <span className="summary-label">Agent:</span>
30
- <span className="summary-value">{agentName}</span>
31
- </div>
32
25
  <div className="summary-item">
33
26
  <span className="summary-label">Worktrees:</span>
34
27
  <span className="summary-value">{worktreeCount}</span>