claude-remote-cli 3.4.2 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/frontend/assets/index-CjI-jAI3.js +50 -0
- package/dist/frontend/assets/{index-m2Bcao6e.css → index-Diib6HQl.css} +1 -1
- package/dist/frontend/index.html +2 -2
- package/dist/server/git.js +0 -4
- package/dist/server/index.js +8 -1
- package/dist/server/output-parsers/claude-parser.js +54 -0
- package/dist/server/output-parsers/codex-parser.js +15 -0
- package/dist/server/output-parsers/index.js +7 -0
- package/dist/server/pty-handler.js +14 -1
- package/dist/server/sessions.js +7 -2
- package/dist/server/ws.js +55 -12
- package/dist/test/output-parser.test.js +95 -0
- package/package.json +1 -1
- package/dist/frontend/assets/index-Dn_qB0Yk.js +0 -50
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.pin-gate.svelte-1qp96yb{display:flex;align-items:center;justify-content:center;height:100vh;background:var(--bg);padding:1rem}.pin-container.svelte-1qp96yb{display:flex;flex-direction:column;align-items:center;gap:1rem;width:100%;max-width:320px;text-align:center}.pin-container.svelte-1qp96yb h1:where(.svelte-1qp96yb){font-size:1.5rem;color:var(--text)}.pin-container.svelte-1qp96yb p:where(.svelte-1qp96yb){color:var(--text-muted);font-size:.95rem}input.svelte-1qp96yb{width:100%;padding:14px 16px;background:var(--surface);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:1.2rem;text-align:center;outline:none;-webkit-appearance:none}input.svelte-1qp96yb:focus{border-color:var(--accent)}button.svelte-1qp96yb{width:100%;padding:14px;background:var(--accent);color:#fff;border:none;border-radius:8px;font-size:1rem;font-weight:600;cursor:pointer;touch-action:manipulation}button.svelte-1qp96yb:active{opacity:.8}.error.svelte-1qp96yb{color:var(--accent);font-size:.9rem}.context-menu-trigger.svelte-1nmhce7{background:none;border:none;color:var(--text-muted);font-size:1rem;font-weight:700;cursor:pointer;padding:0 6px;border-radius:4px;touch-action:manipulation;flex-shrink:0;line-height:1;letter-spacing:1px;min-height:24px;display:inline-flex;align-items:center;transition:color .15s,background .15s}.context-menu-trigger.svelte-1nmhce7:hover{color:var(--text);background:var(--border)}.context-menu-backdrop.svelte-1nmhce7{position:fixed;top:0;right:0;bottom:0;left:0;z-index:999}.context-menu.svelte-1nmhce7{position:fixed;list-style:none;margin:0;padding:4px 0;background:var(--surface);border:1px solid var(--border);border-radius:8px;box-shadow:0 4px 16px #0006;z-index:1000;min-width:175px}.context-menu-item.svelte-1nmhce7{padding:9px 14px;font-size:.85rem;cursor:pointer;color:var(--text);white-space:nowrap}.context-menu-item.svelte-1nmhce7:hover{background:var(--border)}.context-menu-item.svelte-1nmhce7:focus{outline:2px solid var(--accent);outline-offset:-2px}.context-menu-item--danger.svelte-1nmhce7{color:#e74c3c}.context-menu-item--danger.svelte-1nmhce7:hover{background:#e74c3c1f}.context-menu-item--disabled.svelte-1nmhce7{opacity:.4;cursor:default}.context-menu-item--disabled.svelte-1nmhce7:hover{background:none}.workspace-item.svelte-168i8d5{display:flex;flex-direction:column}.workspace-header.svelte-168i8d5{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;cursor:pointer;min-height:44px;transition:background .12s}.workspace-header.svelte-168i8d5:hover{background:var(--surface-hover)}.workspace-item.active.svelte-168i8d5 .workspace-header:where(.svelte-168i8d5){background:var(--surface-hover)}.workspace-left.svelte-168i8d5{display:flex;align-items:center;gap:8px;min-width:0;flex:1}.grip-handle.svelte-168i8d5{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;font-size:.8rem;color:var(--text-muted);cursor:grab;flex-shrink:0;opacity:0;transition:opacity .12s;-webkit-user-select:none;user-select:none}.workspace-header.svelte-168i8d5:hover .grip-handle:where(.svelte-168i8d5){opacity:1}.grip-handle.grip-visible.svelte-168i8d5{opacity:1}.workspace-header.reorder-mode.svelte-168i8d5{cursor:grab}.workspace-header.reorder-mode.svelte-168i8d5:active{cursor:grabbing}.collapse-chevron.svelte-168i8d5{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;font-size:.7rem;color:var(--text-muted);cursor:pointer;flex-shrink:0;transition:color .12s}.collapse-chevron.svelte-168i8d5:hover{color:var(--text)}.collapse-count.svelte-168i8d5{font-size:var(--font-size-xs);font-family:var(--font-mono);color:var(--text-muted);background:var(--border);border-radius:8px;padding:1px 6px;flex-shrink:0}.initial-block.svelte-168i8d5{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:4px;font-size:var(--font-size-xs);font-weight:700;color:#000;font-family:var(--font-mono);flex-shrink:0;line-height:1}.workspace-name.svelte-168i8d5{font-size:var(--font-size-sm);font-weight:700;color:var(--text);font-family:var(--font-mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}.workspace-actions.svelte-168i8d5{display:flex;align-items:center;gap:2px;opacity:0;transition:opacity .12s;flex-shrink:0}.workspace-header.svelte-168i8d5:hover .workspace-actions:where(.svelte-168i8d5){opacity:1}.action-btn.svelte-168i8d5{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:3px;font-size:var(--font-size-xs);color:var(--text-muted);cursor:pointer;font-family:var(--font-mono);transition:background .1s,color .1s}.action-btn.svelte-168i8d5:hover{background:var(--border);color:var(--text)}.session-list.svelte-168i8d5{list-style:none}.session-row.svelte-168i8d5{display:flex;flex-direction:column;gap:2px;padding:6px 10px 6px 36px;cursor:pointer;min-height:44px;font-size:var(--font-size-xs);font-family:var(--font-mono);color:var(--text-muted);transition:background .1s;border-left:3px solid transparent;justify-content:center}.session-row.svelte-168i8d5:hover{background:var(--surface-hover);color:var(--text)}.session-row.selected.svelte-168i8d5{border-left-color:var(--accent);background:var(--surface-hover);color:var(--text)}.session-row.attention.svelte-168i8d5 .session-name:where(.svelte-168i8d5){font-weight:700;color:var(--text)}.session-row-primary.svelte-168i8d5{display:flex;align-items:center;gap:8px;min-width:0}.session-row-secondary.svelte-168i8d5{display:flex;align-items:center;gap:6px;padding-left:15px;font-size:.65rem;color:var(--text-muted);opacity:.7;min-width:0}.secondary-branch.svelte-168i8d5{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}.context-menu-spacer.svelte-168i8d5{flex:1}.secondary-pr.svelte-168i8d5{white-space:nowrap;flex-shrink:0;color:var(--accent)}.secondary-time.svelte-168i8d5{white-space:nowrap;flex-shrink:0;opacity:.7}.session-count-badge.svelte-168i8d5{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;border-radius:8px;background:var(--border);color:var(--text-muted);font-size:.55rem;font-family:var(--font-mono);font-weight:600;padding:0 4px;flex-shrink:0}.diff-badge.svelte-168i8d5{display:inline-flex;align-items:center;gap:4px;font-size:.6rem;flex-shrink:0;margin-left:auto}.diff-add.svelte-168i8d5{color:var(--status-success)}.diff-del.svelte-168i8d5{color:var(--status-error)}.status-dot.svelte-168i8d5{display:inline-block;width:7px;height:7px;border-radius:50%;flex-shrink:0}.status-dot--running.svelte-168i8d5{background:var(--status-success)}.status-dot--idle.svelte-168i8d5{background:var(--status-info)}.dot-inactive.svelte-168i8d5{width:7px;height:7px;border-radius:50%;background:#555;flex-shrink:0}.session-row.inactive.svelte-168i8d5 .session-name:where(.svelte-168i8d5){color:var(--text-muted)}.session-row.inactive.svelte-168i8d5:hover .session-name:where(.svelte-168i8d5){color:var(--text)}.session-row.loading.svelte-168i8d5{pointer-events:none;opacity:.7}.session-row.loading.svelte-168i8d5 .session-name:where(.svelte-168i8d5){color:var(--accent)}.status-dot--attention.svelte-168i8d5{background:var(--status-warning);box-shadow:0 0 5px 1px #fbbf2473;animation:svelte-168i8d5-attention-glow 2s ease-in-out infinite}@keyframes svelte-168i8d5-attention-glow{0%,to{box-shadow:0 0 3px 1px #fbbf244d}50%{box-shadow:0 0 7px 2px #fbbf2499}}.terminal-icon.svelte-168i8d5{font-size:.6rem;font-weight:700;color:var(--text-muted);flex-shrink:0;font-family:var(--font-mono);line-height:1}.session-name.svelte-168i8d5{flex:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;min-width:0}.session-name.bold.svelte-168i8d5{font-weight:700}.add-worktree-row.svelte-168i8d5{padding:4px 10px 6px 36px}.add-worktree-btn.svelte-168i8d5{font-size:var(--font-size-xs);font-family:var(--font-mono);color:var(--text-muted);opacity:.5;cursor:pointer;transition:opacity .1s,color .1s}.add-worktree-btn.svelte-168i8d5:hover{opacity:1;color:var(--text)}.add-worktree-row.disabled.svelte-168i8d5{pointer-events:none}.add-worktree-row.disabled.svelte-168i8d5 .add-worktree-btn:where(.svelte-168i8d5){opacity:.7;color:var(--accent)}.workspace-divider.svelte-168i8d5{height:1px;background:var(--border);margin:0}@media(max-width:600px){.workspace-header.svelte-168i8d5,.session-row.svelte-168i8d5{min-height:48px}.workspace-actions.svelte-168i8d5,.grip-handle.grip-visible.svelte-168i8d5{opacity:1}}.smart-search.svelte-itm1qs{position:relative;flex-shrink:0}.input-row.svelte-itm1qs{display:flex;align-items:center;padding:6px 10px;border-bottom:1px solid var(--border);gap:6px}.prompt.svelte-itm1qs{font-family:var(--font-mono);font-size:var(--font-size-sm);color:var(--accent);flex-shrink:0;line-height:1;-webkit-user-select:none;user-select:none}.search-input.svelte-itm1qs{flex:1;background:transparent;border:none;outline:none;color:var(--text);font-family:var(--font-mono);font-size:var(--font-size-sm);caret-color:var(--accent)}.search-input.svelte-itm1qs::placeholder{color:var(--text-muted);opacity:.5}.dropdown.svelte-itm1qs{position:absolute;top:100%;left:0;right:0;background:var(--surface);border:1px solid var(--border);border-top:none;list-style:none;z-index:200;max-height:240px;overflow-y:auto}.dropdown-item.svelte-itm1qs{display:flex;align-items:baseline;gap:6px;padding:8px 10px;cursor:pointer;font-family:var(--font-mono);font-size:var(--font-size-sm);color:var(--text-muted);transition:background .1s;white-space:nowrap;overflow:hidden}.dropdown-item.svelte-itm1qs:hover,.dropdown-item.focused.svelte-itm1qs{background:var(--surface-hover);color:var(--text)}.dropdown-item.svelte-itm1qs strong:where(.svelte-itm1qs){color:var(--text);font-weight:700}.dropdown-path.svelte-itm1qs{font-size:var(--font-size-xs);color:var(--text-muted);opacity:.5;overflow:hidden;text-overflow:ellipsis;min-width:0;flex-shrink:1}@media(max-width:600px){.smart-search.svelte-itm1qs{width:100%}.input-row.svelte-itm1qs{width:100%;box-sizing:border-box}.search-input.svelte-itm1qs{width:100%;min-width:0}}.sidebar.svelte-owj5vn{position:relative;display:flex;flex-direction:column;background:var(--bg);border-right:1px solid var(--border);overflow:hidden;transition:transform .25s ease,width .2s ease,min-width .2s ease;z-index:100}.resize-handle.svelte-owj5vn{position:absolute;top:0;right:0;width:4px;height:100%;cursor:col-resize;z-index:10;transition:background .15s}.resize-handle.svelte-owj5vn:hover{background:var(--accent)}.sidebar-header.svelte-owj5vn{display:flex;align-items:center;justify-content:space-between;padding:12px 10px;border-bottom:1px solid var(--border);flex-shrink:0}.sidebar-label.svelte-owj5vn{flex:1;font-size:var(--font-size-xs);font-weight:600;color:var(--text-muted);font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.08em}.collapse-btn.svelte-owj5vn{background:none;border:none;color:var(--text-muted);font-size:1.1rem;cursor:pointer;padding:8px 10px;border-radius:4px;flex-shrink:0;line-height:1;font-family:var(--font-mono);min-width:36px;min-height:36px;display:flex;align-items:center;justify-content:center}.collapse-btn.svelte-owj5vn:hover{color:var(--text);background:var(--border)}.sidebar.collapsed.svelte-owj5vn .sidebar-header:where(.svelte-owj5vn){justify-content:center;padding:12px 4px}.icon-btn.svelte-owj5vn{background:none;border:none;color:var(--text);font-size:1.2rem;cursor:pointer;padding:4px 6px;border-radius:4px;touch-action:manipulation;display:none}.icon-btn.svelte-owj5vn:active{background:var(--border)}.workspace-list.svelte-owj5vn{flex:1;overflow-y:auto;overflow-x:hidden}.empty-state.svelte-owj5vn{padding:16px 10px;font-size:var(--font-size-xs);font-family:var(--font-mono);color:var(--text-muted);opacity:.5;text-align:center}.add-workspace-btn.svelte-owj5vn{margin:8px;padding:10px 12px;min-height:40px;background:none;border:1px solid var(--border);border-radius:0;color:var(--text);font-size:var(--font-size-xs);font-family:var(--font-mono);cursor:pointer;touch-action:manipulation;text-align:center;flex-shrink:0;transition:background .1s,border-color .1s}.add-workspace-btn.svelte-owj5vn{border-color:var(--accent);color:var(--accent)}.add-workspace-btn.svelte-owj5vn:hover{background:color-mix(in srgb,var(--accent) 12%,transparent)}.add-workspace-btn.svelte-owj5vn:active{background:var(--border)}.done-reorder-btn.svelte-owj5vn{margin:8px;padding:10px 12px;min-height:40px;background:none;border:1px solid var(--accent);border-radius:0;color:var(--accent);font-size:var(--font-size-xs);font-family:var(--font-mono);cursor:pointer;touch-action:manipulation;text-align:center;flex-shrink:0;transition:background .1s}.done-reorder-btn.svelte-owj5vn:hover{background:color-mix(in srgb,var(--accent) 12%,transparent)}.done-reorder-btn.svelte-owj5vn:active{background:var(--border)}.settings-btn.svelte-owj5vn{margin:0 8px 8px;padding:10px 12px;min-height:40px;background:none;border:1px solid var(--border);border-radius:0;color:var(--text-muted);font-size:var(--font-size-xs);font-family:var(--font-mono);cursor:pointer;touch-action:manipulation;text-align:center;flex-shrink:0;transition:background .1s}.settings-btn.svelte-owj5vn:hover{background:var(--surface-hover);color:var(--text)}.settings-btn.svelte-owj5vn:active{background:var(--border)}@media(max-width:600px){.sidebar.svelte-owj5vn{position:fixed;top:0;left:0;height:100%;transform:translate(-100%);box-shadow:2px 0 12px #00000080;transition:transform .25s ease}.sidebar.open.svelte-owj5vn{transform:translate(0)}.collapse-btn.svelte-owj5vn{display:none}.icon-btn.svelte-owj5vn{display:block;font-size:1.4rem;padding:4px 8px}.resize-handle.svelte-owj5vn{display:none}}/**
|
|
1
|
+
.pin-gate.svelte-1qp96yb{display:flex;align-items:center;justify-content:center;height:100vh;background:var(--bg);padding:1rem}.pin-container.svelte-1qp96yb{display:flex;flex-direction:column;align-items:center;gap:1rem;width:100%;max-width:320px;text-align:center}.pin-container.svelte-1qp96yb h1:where(.svelte-1qp96yb){font-size:1.5rem;color:var(--text)}.pin-container.svelte-1qp96yb p:where(.svelte-1qp96yb){color:var(--text-muted);font-size:.95rem}input.svelte-1qp96yb{width:100%;padding:14px 16px;background:var(--surface);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:1.2rem;text-align:center;outline:none;-webkit-appearance:none}input.svelte-1qp96yb:focus{border-color:var(--accent)}button.svelte-1qp96yb{width:100%;padding:14px;background:var(--accent);color:#fff;border:none;border-radius:8px;font-size:1rem;font-weight:600;cursor:pointer;touch-action:manipulation}button.svelte-1qp96yb:active{opacity:.8}.error.svelte-1qp96yb{color:var(--accent);font-size:.9rem}.context-menu-trigger.svelte-1nmhce7{background:none;border:none;color:var(--text-muted);font-size:1rem;font-weight:700;cursor:pointer;padding:0 6px;border-radius:4px;touch-action:manipulation;flex-shrink:0;line-height:1;letter-spacing:1px;min-height:24px;display:inline-flex;align-items:center;transition:color .15s,background .15s}.context-menu-trigger.svelte-1nmhce7:hover{color:var(--text);background:var(--border)}.context-menu-backdrop.svelte-1nmhce7{position:fixed;top:0;right:0;bottom:0;left:0;z-index:999}.context-menu.svelte-1nmhce7{position:fixed;list-style:none;margin:0;padding:4px 0;background:var(--surface);border:1px solid var(--border);border-radius:8px;box-shadow:0 4px 16px #0006;z-index:1000;min-width:175px}.context-menu-item.svelte-1nmhce7{padding:9px 14px;font-size:.85rem;cursor:pointer;color:var(--text);white-space:nowrap}.context-menu-item.svelte-1nmhce7:hover{background:var(--border)}.context-menu-item.svelte-1nmhce7:focus{outline:2px solid var(--accent);outline-offset:-2px}.context-menu-item--danger.svelte-1nmhce7{color:#e74c3c}.context-menu-item--danger.svelte-1nmhce7:hover{background:#e74c3c1f}.context-menu-item--disabled.svelte-1nmhce7{opacity:.4;cursor:default}.context-menu-item--disabled.svelte-1nmhce7:hover{background:none}.workspace-item.svelte-168i8d5{display:flex;flex-direction:column}.workspace-header.svelte-168i8d5{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;cursor:pointer;min-height:44px;transition:background .12s}.workspace-header.svelte-168i8d5:hover{background:var(--surface-hover)}.workspace-item.active.svelte-168i8d5 .workspace-header:where(.svelte-168i8d5){background:var(--surface-hover)}.workspace-left.svelte-168i8d5{display:flex;align-items:center;gap:8px;min-width:0;flex:1}.workspace-header.reorder-mode.svelte-168i8d5{cursor:grab}.workspace-header.reorder-mode.svelte-168i8d5:active{cursor:grabbing}.collapse-chevron.svelte-168i8d5{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;font-size:.7rem;color:var(--text-muted);cursor:pointer;flex-shrink:0;transition:color .12s}.collapse-chevron.svelte-168i8d5:hover{color:var(--text)}.collapse-count.svelte-168i8d5{font-size:var(--font-size-xs);font-family:var(--font-mono);color:var(--text-muted);background:var(--border);border-radius:8px;padding:1px 6px;flex-shrink:0}.initial-block.svelte-168i8d5{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:4px;font-size:var(--font-size-xs);font-weight:700;color:#000;font-family:var(--font-mono);flex-shrink:0;line-height:1}.workspace-name.svelte-168i8d5{font-size:var(--font-size-sm);font-weight:700;color:var(--text);font-family:var(--font-mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}.workspace-actions.svelte-168i8d5{display:flex;align-items:center;gap:2px;opacity:0;transition:opacity .12s;flex-shrink:0}.workspace-header.svelte-168i8d5:hover .workspace-actions:where(.svelte-168i8d5){opacity:1}.action-btn.svelte-168i8d5{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:3px;font-size:var(--font-size-xs);color:var(--text-muted);cursor:pointer;font-family:var(--font-mono);transition:background .1s,color .1s}.action-btn.svelte-168i8d5:hover{background:var(--border);color:var(--text)}.session-list.svelte-168i8d5{list-style:none}.session-row.svelte-168i8d5{position:relative;display:flex;flex-direction:column;gap:2px;padding:6px 10px 6px 36px;cursor:pointer;min-height:44px;font-size:var(--font-size-xs);font-family:var(--font-mono);color:var(--text-muted);transition:background .1s;border-left:3px solid transparent;justify-content:center}.session-row.svelte-168i8d5:hover{background:var(--surface-hover);color:var(--text)}.session-row.selected.svelte-168i8d5{border-left-color:var(--accent);background:var(--surface-hover);color:var(--text)}.session-row.attention.svelte-168i8d5 .session-name:where(.svelte-168i8d5){font-weight:700;color:var(--text)}.session-row-primary.svelte-168i8d5{display:flex;align-items:center;gap:8px;min-width:0}.session-row-secondary.svelte-168i8d5{display:flex;align-items:center;gap:6px;padding-left:15px;font-size:.65rem;color:var(--text-muted);min-width:0}.secondary-branch.svelte-168i8d5{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}.row-menu-overlay.svelte-168i8d5{position:absolute;right:8px;top:50%;transform:translateY(-50%);opacity:0;transition:opacity .12s;z-index:2}.session-row.svelte-168i8d5:hover .row-menu-overlay:where(.svelte-168i8d5){opacity:1}.secondary-pr.svelte-168i8d5{white-space:nowrap;flex-shrink:0;color:var(--accent)}.secondary-time.svelte-168i8d5{white-space:nowrap;flex-shrink:0;opacity:.7}.session-count-badge.svelte-168i8d5{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;border-radius:8px;background:var(--border);color:var(--text-muted);font-size:.55rem;font-family:var(--font-mono);font-weight:600;padding:0 4px;flex-shrink:0}.diff-badge.svelte-168i8d5{display:inline-flex;align-items:center;gap:4px;font-size:.6rem;flex-shrink:0;margin-left:auto}.diff-add.svelte-168i8d5{color:var(--status-success)}.diff-del.svelte-168i8d5{color:var(--status-error)}.status-dot.svelte-168i8d5{display:inline-block;width:7px;height:7px;border-radius:50%;flex-shrink:0}.status-dot--running.svelte-168i8d5{background:var(--status-success)}.status-dot--idle.svelte-168i8d5{background:var(--status-info)}.dot-inactive.svelte-168i8d5{width:7px;height:7px;border-radius:50%;background:#555;flex-shrink:0}.session-row.inactive.svelte-168i8d5 .session-name:where(.svelte-168i8d5){color:var(--text-muted)}.session-row.inactive.svelte-168i8d5:hover .session-name:where(.svelte-168i8d5){color:var(--text)}.session-row.loading.svelte-168i8d5{pointer-events:none;opacity:.7}.session-row.loading.svelte-168i8d5 .session-name:where(.svelte-168i8d5){color:var(--accent)}.status-dot--attention.svelte-168i8d5{background:var(--status-warning);box-shadow:0 0 5px 1px #fbbf2473;animation:svelte-168i8d5-attention-glow 2s ease-in-out infinite}.status-dot--permission-prompt.svelte-168i8d5{background:#eab308;box-shadow:0 0 5px 1px #eab30873;animation:svelte-168i8d5-attention-glow 1.5s ease-in-out infinite}@keyframes svelte-168i8d5-attention-glow{0%,to{box-shadow:0 0 3px 1px #fbbf244d}50%{box-shadow:0 0 7px 2px #fbbf2499}}.terminal-icon.svelte-168i8d5{font-size:.6rem;font-weight:700;color:var(--text-muted);flex-shrink:0;font-family:var(--font-mono);line-height:1}.session-name.svelte-168i8d5{flex:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;min-width:0}.session-name.bold.svelte-168i8d5{font-weight:700}.add-worktree-row.svelte-168i8d5{padding:4px 10px 6px 36px}.add-worktree-btn.svelte-168i8d5{font-size:var(--font-size-xs);font-family:var(--font-mono);color:var(--text-muted);opacity:.5;cursor:pointer;transition:opacity .1s,color .1s}.add-worktree-btn.svelte-168i8d5:hover{opacity:1;color:var(--text)}.add-worktree-row.disabled.svelte-168i8d5{pointer-events:none}.add-worktree-row.disabled.svelte-168i8d5 .add-worktree-btn:where(.svelte-168i8d5){opacity:.7;color:var(--accent)}.workspace-divider.svelte-168i8d5{height:1px;background:var(--border);margin:0}@media(max-width:600px){.workspace-header.svelte-168i8d5,.session-row.svelte-168i8d5{min-height:48px}.workspace-actions.svelte-168i8d5{opacity:1}.row-menu-overlay.svelte-168i8d5{display:none}}.smart-search.svelte-itm1qs{position:relative;flex-shrink:0}.input-row.svelte-itm1qs{display:flex;align-items:center;padding:6px 10px;border-bottom:1px solid var(--border);gap:6px}.prompt.svelte-itm1qs{font-family:var(--font-mono);font-size:var(--font-size-sm);color:var(--accent);flex-shrink:0;line-height:1;-webkit-user-select:none;user-select:none}.search-input.svelte-itm1qs{flex:1;background:transparent;border:none;outline:none;color:var(--text);font-family:var(--font-mono);font-size:var(--font-size-sm);caret-color:var(--accent)}.search-input.svelte-itm1qs::placeholder{color:var(--text-muted);opacity:.5}.dropdown.svelte-itm1qs{position:absolute;top:100%;left:0;right:0;background:var(--surface);border:1px solid var(--border);border-top:none;list-style:none;z-index:200;max-height:240px;overflow-y:auto}.dropdown-item.svelte-itm1qs{display:flex;align-items:baseline;gap:6px;padding:8px 10px;cursor:pointer;font-family:var(--font-mono);font-size:var(--font-size-sm);color:var(--text-muted);transition:background .1s;white-space:nowrap;overflow:hidden}.dropdown-item.svelte-itm1qs:hover,.dropdown-item.focused.svelte-itm1qs{background:var(--surface-hover);color:var(--text)}.dropdown-item.svelte-itm1qs strong:where(.svelte-itm1qs){color:var(--text);font-weight:700}.dropdown-path.svelte-itm1qs{font-size:var(--font-size-xs);color:var(--text-muted);opacity:.5;overflow:hidden;text-overflow:ellipsis;min-width:0;flex-shrink:1}@media(max-width:600px){.smart-search.svelte-itm1qs{width:100%}.input-row.svelte-itm1qs{width:100%;box-sizing:border-box}.search-input.svelte-itm1qs{width:100%;min-width:0}}.sidebar.svelte-owj5vn{position:relative;display:flex;flex-direction:column;background:var(--bg);border-right:1px solid var(--border);overflow:hidden;transition:transform .25s ease,width .2s ease,min-width .2s ease;z-index:100}.resize-handle.svelte-owj5vn{position:absolute;top:0;right:0;width:4px;height:100%;cursor:col-resize;z-index:10;transition:background .15s}.resize-handle.svelte-owj5vn:hover{background:var(--accent)}.sidebar-header.svelte-owj5vn{display:flex;align-items:center;justify-content:space-between;padding:12px 10px;border-bottom:1px solid var(--border);flex-shrink:0}.sidebar-label.svelte-owj5vn{flex:1;font-size:var(--font-size-xs);font-weight:600;color:var(--text-muted);font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.08em}.collapse-btn.svelte-owj5vn{background:none;border:none;color:var(--text-muted);font-size:1.1rem;cursor:pointer;padding:8px 10px;border-radius:4px;flex-shrink:0;line-height:1;font-family:var(--font-mono);min-width:36px;min-height:36px;display:flex;align-items:center;justify-content:center}.collapse-btn.svelte-owj5vn:hover{color:var(--text);background:var(--border)}.sidebar.collapsed.svelte-owj5vn .sidebar-header:where(.svelte-owj5vn){justify-content:center;padding:12px 4px}.icon-btn.svelte-owj5vn{background:none;border:none;color:var(--text);font-size:1.2rem;cursor:pointer;padding:4px 6px;border-radius:4px;touch-action:manipulation;display:none}.icon-btn.svelte-owj5vn:active{background:var(--border)}.workspace-list.svelte-owj5vn{flex:1;overflow-y:auto;overflow-x:hidden}.empty-state.svelte-owj5vn{padding:16px 10px;font-size:var(--font-size-xs);font-family:var(--font-mono);color:var(--text-muted);opacity:.5;text-align:center}.sidebar-footer-row.svelte-owj5vn{display:flex;gap:8px;margin:8px;align-items:stretch;flex-shrink:0}.add-workspace-btn.svelte-owj5vn{flex:1;padding:10px 12px;min-height:40px;background:none;border:1px solid var(--accent);border-radius:0;color:var(--accent);font-size:var(--font-size-xs);font-family:var(--font-mono);cursor:pointer;touch-action:manipulation;text-align:center;transition:background .1s}.add-workspace-btn.svelte-owj5vn:hover{background:color-mix(in srgb,var(--accent) 12%,transparent)}.add-workspace-btn.svelte-owj5vn:active{background:var(--border)}.done-reorder-btn.svelte-owj5vn{margin:8px;padding:10px 12px;min-height:40px;background:none;border:1px solid var(--accent);border-radius:0;color:var(--accent);font-size:var(--font-size-xs);font-family:var(--font-mono);cursor:pointer;touch-action:manipulation;text-align:center;flex-shrink:0;transition:background .1s}.done-reorder-btn.svelte-owj5vn:hover{background:color-mix(in srgb,var(--accent) 12%,transparent)}.done-reorder-btn.svelte-owj5vn:active{background:var(--border)}.settings-icon-btn.svelte-owj5vn{width:40px;min-height:40px;background:none;border:1px solid var(--border);border-radius:0;color:var(--text-muted);font-size:1rem;cursor:pointer;touch-action:manipulation;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .1s,color .1s}.settings-icon-btn.svelte-owj5vn:hover{background:var(--surface-hover);color:var(--text)}.settings-icon-btn.svelte-owj5vn:active{background:var(--border)}@media(max-width:600px){.sidebar.svelte-owj5vn{position:fixed;top:0;left:0;height:100%;transform:translate(-100%);box-shadow:2px 0 12px #00000080;transition:transform .25s ease}.sidebar.open.svelte-owj5vn{transform:translate(0)}.collapse-btn.svelte-owj5vn{display:none}.icon-btn.svelte-owj5vn{display:block;font-size:1.4rem;padding:4px 8px}.resize-handle.svelte-owj5vn{display:none}}/**
|
|
2
2
|
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
3
3
|
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
|
4
4
|
* https://github.com/chjj/term.js
|
package/dist/frontend/index.html
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
12
12
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
13
13
|
<meta name="theme-color" content="#1a1a1a" />
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-CjI-jAI3.js"></script>
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Diib6HQl.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="app"></div>
|
package/dist/server/git.js
CHANGED
|
@@ -178,10 +178,6 @@ async function getPrForBranch(repoPath, branch, options = {}) {
|
|
|
178
178
|
return null;
|
|
179
179
|
try {
|
|
180
180
|
const data = JSON.parse(stdout);
|
|
181
|
-
// Only return OPEN PRs — gh pr view returns merged/closed PRs too
|
|
182
|
-
if (data.state !== 'OPEN') {
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
181
|
return {
|
|
186
182
|
number: data.number,
|
|
187
183
|
title: data.title,
|
package/dist/server/index.js
CHANGED
|
@@ -741,7 +741,7 @@ async function main() {
|
|
|
741
741
|
res.status(201).json(session);
|
|
742
742
|
});
|
|
743
743
|
// POST /sessions/repo — start a session in the repo root (no worktree)
|
|
744
|
-
app.post('/sessions/repo', requireAuth, (req, res) => {
|
|
744
|
+
app.post('/sessions/repo', requireAuth, async (req, res) => {
|
|
745
745
|
const { repoPath, repoName, continue: continueSession, claudeArgs, yolo, agent, useTmux, cols, rows } = req.body;
|
|
746
746
|
if (!repoPath) {
|
|
747
747
|
res.status(400).json({ error: 'repoPath is required' });
|
|
@@ -761,6 +761,12 @@ async function main() {
|
|
|
761
761
|
const args = continueSession ? [...AGENT_CONTINUE_ARGS[resolvedAgent], ...baseArgs] : [...baseArgs];
|
|
762
762
|
const roots = config.rootDirs || [];
|
|
763
763
|
const root = roots.find(function (r) { return repoPath.startsWith(r); }) || '';
|
|
764
|
+
let branchName = '';
|
|
765
|
+
try {
|
|
766
|
+
const { stdout } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: repoPath });
|
|
767
|
+
branchName = stdout.trim();
|
|
768
|
+
}
|
|
769
|
+
catch { /* non-fatal */ }
|
|
764
770
|
const session = sessions.create({
|
|
765
771
|
type: 'repo',
|
|
766
772
|
agent: resolvedAgent,
|
|
@@ -770,6 +776,7 @@ async function main() {
|
|
|
770
776
|
root,
|
|
771
777
|
displayName: name,
|
|
772
778
|
args,
|
|
779
|
+
branchName,
|
|
773
780
|
useTmux: useTmux ?? config.launchInTmux,
|
|
774
781
|
...(safeCols != null && { cols: safeCols }),
|
|
775
782
|
...(safeRows != null && { rows: safeRows }),
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Strip ANSI escape sequences (CSI, OSC, charset, mode sequences)
|
|
2
|
+
const ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\x1b[()][AB012]|\x1b\[\?[0-9;]*[hlm]|\x1b\[[0-9]*[ABCDJKH]/g;
|
|
3
|
+
/**
|
|
4
|
+
* Claude Code output parser.
|
|
5
|
+
*
|
|
6
|
+
* Claude Code uses a React/Ink TUI. This parser detects semantic state
|
|
7
|
+
* transitions by pattern-matching on cleaned terminal output.
|
|
8
|
+
*/
|
|
9
|
+
export class ClaudeOutputParser {
|
|
10
|
+
currentState = 'initializing';
|
|
11
|
+
hasSeenFirstPrompt = false;
|
|
12
|
+
onData(chunk, _recentScrollback) {
|
|
13
|
+
const clean = chunk.replace(ANSI_RE, '');
|
|
14
|
+
if (!clean.trim())
|
|
15
|
+
return null;
|
|
16
|
+
const newState = this.classify(clean);
|
|
17
|
+
if (newState && newState !== this.currentState) {
|
|
18
|
+
this.currentState = newState;
|
|
19
|
+
return { state: newState };
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
reset() {
|
|
24
|
+
this.currentState = 'initializing';
|
|
25
|
+
this.hasSeenFirstPrompt = false;
|
|
26
|
+
}
|
|
27
|
+
get state() {
|
|
28
|
+
return this.currentState;
|
|
29
|
+
}
|
|
30
|
+
classify(clean) {
|
|
31
|
+
// Permission prompt detection (highest priority)
|
|
32
|
+
if (/\bAllow\b/.test(clean) && /\bDeny\b/.test(clean)) {
|
|
33
|
+
return 'permission-prompt';
|
|
34
|
+
}
|
|
35
|
+
// Error detection
|
|
36
|
+
if (/^Error:|^ERROR:|✗\s|error:/m.test(clean)) {
|
|
37
|
+
return 'error';
|
|
38
|
+
}
|
|
39
|
+
// Waiting for input: the ">" prompt on its own line, or initial greeting
|
|
40
|
+
if (/^>\s*$/m.test(clean) || /How can I help/i.test(clean) || /What would you like/i.test(clean)) {
|
|
41
|
+
this.hasSeenFirstPrompt = true;
|
|
42
|
+
return 'waiting-for-input';
|
|
43
|
+
}
|
|
44
|
+
// Processing: substantive output after the first prompt
|
|
45
|
+
if (this.hasSeenFirstPrompt && clean.trim().length > 0) {
|
|
46
|
+
return 'processing';
|
|
47
|
+
}
|
|
48
|
+
// Still initializing if we haven't seen the first prompt
|
|
49
|
+
if (!this.hasSeenFirstPrompt) {
|
|
50
|
+
return 'initializing';
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex output parser — stub.
|
|
3
|
+
*
|
|
4
|
+
* Always returns null, which tells the system to fall back to
|
|
5
|
+
* timer-based idle detection. Replace with real pattern matching
|
|
6
|
+
* when Codex TUI patterns are mapped.
|
|
7
|
+
*/
|
|
8
|
+
export class CodexOutputParser {
|
|
9
|
+
onData(_chunk, _recentScrollback) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
reset() {
|
|
13
|
+
// No state to reset
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ClaudeOutputParser } from './claude-parser.js';
|
|
2
|
+
import { CodexOutputParser } from './codex-parser.js';
|
|
3
|
+
/** Registry: factory per agent type */
|
|
4
|
+
export const outputParsers = {
|
|
5
|
+
claude: () => new ClaudeOutputParser(),
|
|
6
|
+
codex: () => new CodexOutputParser(),
|
|
7
|
+
};
|
|
@@ -5,6 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import { AGENT_COMMANDS, AGENT_CONTINUE_ARGS } from './types.js';
|
|
6
6
|
import { readMeta, writeMeta } from './config.js';
|
|
7
7
|
import { fireSessionEnd } from './sessions.js';
|
|
8
|
+
import { outputParsers } from './output-parsers/index.js';
|
|
8
9
|
const IDLE_TIMEOUT_MS = 5000;
|
|
9
10
|
const MAX_SCROLLBACK = 256 * 1024; // 256KB max
|
|
10
11
|
export function generateTmuxSessionName(displayName, id) {
|
|
@@ -22,7 +23,7 @@ export function resolveTmuxSpawn(command, args, tmuxSessionName) {
|
|
|
22
23
|
],
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
|
-
export function createPtySession(params, sessionsMap, idleChangeCallbacks) {
|
|
26
|
+
export function createPtySession(params, sessionsMap, idleChangeCallbacks, stateChangeCallbacks = []) {
|
|
26
27
|
const { id, type, agent = 'claude', repoName, repoPath, cwd, root, worktreeName, branchName, displayName, command, args = [], cols = 80, rows = 24, configPath, useTmux: paramUseTmux, tmuxSessionName: paramTmuxSessionName, initialScrollback, restored: paramRestored, } = params;
|
|
27
28
|
const createdAt = new Date().toISOString();
|
|
28
29
|
const resolvedCommand = command || AGENT_COMMANDS[agent];
|
|
@@ -49,6 +50,8 @@ export function createPtySession(params, sessionsMap, idleChangeCallbacks) {
|
|
|
49
50
|
const scrollback = initialScrollback ? [...initialScrollback] : [];
|
|
50
51
|
let scrollbackBytes = initialScrollback ? initialScrollback.reduce((sum, s) => sum + s.length, 0) : 0;
|
|
51
52
|
const resolvedCwd = cwd || repoPath;
|
|
53
|
+
// Instantiate vendor-specific output parser (terminal/custom-command sessions get no parser)
|
|
54
|
+
const parser = command ? outputParsers['claude']() : outputParsers[agent]();
|
|
52
55
|
const session = {
|
|
53
56
|
id,
|
|
54
57
|
type: type || 'worktree',
|
|
@@ -73,6 +76,8 @@ export function createPtySession(params, sessionsMap, idleChangeCallbacks) {
|
|
|
73
76
|
status: 'active',
|
|
74
77
|
restored: paramRestored || false,
|
|
75
78
|
needsBranchRename: false,
|
|
79
|
+
agentState: 'initializing',
|
|
80
|
+
outputParser: parser,
|
|
76
81
|
};
|
|
77
82
|
sessionsMap.set(id, session);
|
|
78
83
|
// Load existing metadata to preserve a previously-set displayName
|
|
@@ -121,6 +126,13 @@ export function createPtySession(params, sessionsMap, idleChangeCallbacks) {
|
|
|
121
126
|
writeMeta(configPath, { worktreePath: repoPath, displayName: session.displayName, lastActivity: session.lastActivity });
|
|
122
127
|
}, 5000);
|
|
123
128
|
}
|
|
129
|
+
// Vendor-specific output parsing for semantic state detection
|
|
130
|
+
const parseResult = session.outputParser.onData(data, scrollback.slice(-20));
|
|
131
|
+
if (parseResult && parseResult.state !== session.agentState) {
|
|
132
|
+
session.agentState = parseResult.state;
|
|
133
|
+
for (const cb of stateChangeCallbacks)
|
|
134
|
+
cb(session.id, parseResult.state);
|
|
135
|
+
}
|
|
124
136
|
});
|
|
125
137
|
proc.onExit(() => {
|
|
126
138
|
if (canRetry && (Date.now() - spawnTime) < 3000) {
|
|
@@ -213,6 +225,7 @@ export function createPtySession(params, sessionsMap, idleChangeCallbacks) {
|
|
|
213
225
|
tmuxSessionName,
|
|
214
226
|
status: 'active',
|
|
215
227
|
needsBranchRename: false,
|
|
228
|
+
agentState: 'initializing',
|
|
216
229
|
};
|
|
217
230
|
return { session, result };
|
|
218
231
|
}
|
package/dist/server/sessions.js
CHANGED
|
@@ -17,6 +17,10 @@ const idleChangeCallbacks = [];
|
|
|
17
17
|
function onIdleChange(cb) {
|
|
18
18
|
idleChangeCallbacks.push(cb);
|
|
19
19
|
}
|
|
20
|
+
const stateChangeCallbacks = [];
|
|
21
|
+
function onStateChange(cb) {
|
|
22
|
+
stateChangeCallbacks.push(cb);
|
|
23
|
+
}
|
|
20
24
|
const sessionEndCallbacks = [];
|
|
21
25
|
function onSessionEnd(cb) {
|
|
22
26
|
sessionEndCallbacks.push(cb);
|
|
@@ -49,7 +53,7 @@ function create({ id: providedId, type, agent = 'claude', repoName, repoPath, cw
|
|
|
49
53
|
initialScrollback,
|
|
50
54
|
restored: paramRestored,
|
|
51
55
|
};
|
|
52
|
-
const { session: ptySession, result } = createPtySession(ptyParams, sessions, idleChangeCallbacks);
|
|
56
|
+
const { session: ptySession, result } = createPtySession(ptyParams, sessions, idleChangeCallbacks, stateChangeCallbacks);
|
|
53
57
|
if (paramNeedsBranchRename) {
|
|
54
58
|
ptySession.needsBranchRename = true;
|
|
55
59
|
}
|
|
@@ -83,6 +87,7 @@ function list() {
|
|
|
83
87
|
tmuxSessionName: s.tmuxSessionName,
|
|
84
88
|
status: s.status,
|
|
85
89
|
needsBranchRename: !!s.needsBranchRename,
|
|
90
|
+
agentState: s.agentState,
|
|
86
91
|
}))
|
|
87
92
|
.sort((a, b) => b.lastActivity.localeCompare(a.lastActivity));
|
|
88
93
|
}
|
|
@@ -343,4 +348,4 @@ async function populateMetaCache() {
|
|
|
343
348
|
}
|
|
344
349
|
// Re-export pty-handler utilities for backward compatibility
|
|
345
350
|
export { generateTmuxSessionName, resolveTmuxSpawn } from './pty-handler.js';
|
|
346
|
-
export { create, get, list, kill, killAllTmuxSessions, resize, updateDisplayName, write, onIdleChange, onSessionEnd, fireSessionEnd, findRepoSession, nextTerminalName, serializeAll, restoreFromDisk, activeTmuxSessionNames, getSessionMeta, getAllSessionMeta, populateMetaCache, AGENT_COMMANDS, AGENT_CONTINUE_ARGS, AGENT_YOLO_ARGS };
|
|
351
|
+
export { create, get, list, kill, killAllTmuxSessions, resize, updateDisplayName, write, onIdleChange, onStateChange, onSessionEnd, fireSessionEnd, findRepoSession, nextTerminalName, serializeAll, restoreFromDisk, activeTmuxSessionNames, getSessionMeta, getAllSessionMeta, populateMetaCache, AGENT_COMMANDS, AGENT_CONTINUE_ARGS, AGENT_YOLO_ARGS };
|
package/dist/server/ws.js
CHANGED
|
@@ -38,6 +38,47 @@ function startBranchWatcher(session, broadcastEvent, cfgPath) {
|
|
|
38
38
|
}
|
|
39
39
|
}, BRANCH_POLL_INTERVAL_MS);
|
|
40
40
|
}
|
|
41
|
+
/** Sideband branch rename: uses headless claude to generate a branch name from the first message */
|
|
42
|
+
async function spawnBranchRename(session, firstMessage, cfgPath, broadcastEvent) {
|
|
43
|
+
try {
|
|
44
|
+
const cleanMessage = firstMessage.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '').replace(/[\x00-\x1f]/g, ' ').trim();
|
|
45
|
+
if (!cleanMessage)
|
|
46
|
+
return;
|
|
47
|
+
const basePrompt = session.branchRenamePrompt
|
|
48
|
+
?? `Output ONLY a short kebab-case git branch name (no explanation, no backticks, no prefix, just the name) that describes this task:`;
|
|
49
|
+
const prompt = `${basePrompt}\n\n${cleanMessage.slice(0, 500)}`;
|
|
50
|
+
const { stdout } = await execFileAsync('claude', ['-p', '--model', 'haiku', prompt], {
|
|
51
|
+
cwd: session.cwd,
|
|
52
|
+
timeout: 30000,
|
|
53
|
+
});
|
|
54
|
+
const branchName = stdout.trim().replace(/`/g, '').replace(/[^a-z0-9-]/gi, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').toLowerCase().slice(0, 60);
|
|
55
|
+
if (!branchName)
|
|
56
|
+
return;
|
|
57
|
+
await execFileAsync('git', ['branch', '-m', branchName], { cwd: session.cwd });
|
|
58
|
+
// Update session state
|
|
59
|
+
const displayName = branchToDisplayName(branchName);
|
|
60
|
+
session.branchName = branchName;
|
|
61
|
+
session.displayName = displayName;
|
|
62
|
+
broadcastEvent('session-renamed', {
|
|
63
|
+
sessionId: session.id,
|
|
64
|
+
branchName,
|
|
65
|
+
displayName,
|
|
66
|
+
});
|
|
67
|
+
if (cfgPath) {
|
|
68
|
+
writeMeta(cfgPath, {
|
|
69
|
+
worktreePath: session.repoPath,
|
|
70
|
+
displayName,
|
|
71
|
+
lastActivity: new Date().toISOString(),
|
|
72
|
+
branchName,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Sideband rename is best-effort — fall back to branch watcher if claude CLI isn't available
|
|
78
|
+
if (cfgPath)
|
|
79
|
+
startBranchWatcher(session, broadcastEvent, cfgPath);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
41
82
|
function parseCookies(cookieHeader) {
|
|
42
83
|
const cookies = {};
|
|
43
84
|
if (!cookieHeader)
|
|
@@ -143,29 +184,28 @@ function setupWebSocket(server, authenticatedTokens, watcher, configPath) {
|
|
|
143
184
|
}
|
|
144
185
|
}
|
|
145
186
|
catch (_) { }
|
|
146
|
-
//
|
|
187
|
+
// Sideband branch rename: capture first message, pass through unmodified, rename out-of-band
|
|
188
|
+
if (ptySession.needsBranchRename && ptySession.agentState !== 'waiting-for-input') {
|
|
189
|
+
ptySession.pty.write(str);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
147
192
|
if (ptySession.needsBranchRename) {
|
|
148
193
|
if (!ptySession._renameBuffer)
|
|
149
194
|
ptySession._renameBuffer = '';
|
|
150
195
|
const enterIndex = str.indexOf('\r');
|
|
151
196
|
if (enterIndex === -1) {
|
|
152
197
|
ptySession._renameBuffer += str;
|
|
153
|
-
ptySession.pty.write(str); //
|
|
198
|
+
ptySession.pty.write(str); // pass through to PTY normally — user sees their typing
|
|
154
199
|
return;
|
|
155
200
|
}
|
|
156
|
-
// Enter detected —
|
|
201
|
+
// Enter detected — pass everything through unmodified
|
|
157
202
|
const buffered = ptySession._renameBuffer;
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
// Clear the echoed input line before writing the full prompt+message
|
|
161
|
-
const clearLine = '\r' + ' '.repeat(Math.min(beforeEnter.length + 2, 200)) + '\r';
|
|
162
|
-
ptySession.pty.write(clearLine);
|
|
163
|
-
const renamePrompt = `Before doing anything else, rename the current git branch using \`git branch -m <new-name>\`. Choose a short, descriptive kebab-case branch name based on the task below.${ptySession.branchRenamePrompt ? ' User preferences: ' + ptySession.branchRenamePrompt : ''} Do not ask for confirmation — just rename and proceed.\n\n`;
|
|
164
|
-
ptySession.pty.write(renamePrompt + beforeEnter + afterEnter);
|
|
203
|
+
const firstMessage = buffered + str.slice(0, enterIndex);
|
|
204
|
+
ptySession.pty.write(str); // pass through the Enter key
|
|
165
205
|
ptySession.needsBranchRename = false;
|
|
166
206
|
delete ptySession._renameBuffer;
|
|
167
|
-
|
|
168
|
-
|
|
207
|
+
// Sideband: spawn headless claude to generate branch name (async, non-blocking)
|
|
208
|
+
spawnBranchRename(ptySession, firstMessage, configPath, broadcastEvent);
|
|
169
209
|
return;
|
|
170
210
|
}
|
|
171
211
|
// Use ptySession.pty dynamically so writes go to current PTY
|
|
@@ -182,6 +222,9 @@ function setupWebSocket(server, authenticatedTokens, watcher, configPath) {
|
|
|
182
222
|
sessions.onIdleChange((sessionId, idle) => {
|
|
183
223
|
broadcastEvent('session-idle-changed', { sessionId, idle });
|
|
184
224
|
});
|
|
225
|
+
sessions.onStateChange((sessionId, state) => {
|
|
226
|
+
broadcastEvent('session-state-changed', { sessionId, state });
|
|
227
|
+
});
|
|
185
228
|
sessions.onSessionEnd((sessionId, repoPath, branchName) => {
|
|
186
229
|
broadcastEvent('session-ended', { sessionId, repoPath, branchName });
|
|
187
230
|
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { ClaudeOutputParser } from '../server/output-parsers/claude-parser.js';
|
|
4
|
+
import { CodexOutputParser } from '../server/output-parsers/codex-parser.js';
|
|
5
|
+
import { outputParsers } from '../server/output-parsers/index.js';
|
|
6
|
+
describe('ClaudeOutputParser', () => {
|
|
7
|
+
it('starts in initializing state', () => {
|
|
8
|
+
const parser = new ClaudeOutputParser();
|
|
9
|
+
assert.equal(parser.state, 'initializing');
|
|
10
|
+
});
|
|
11
|
+
it('transitions to waiting-for-input on > prompt', () => {
|
|
12
|
+
const parser = new ClaudeOutputParser();
|
|
13
|
+
const result = parser.onData('>\n', []);
|
|
14
|
+
assert.deepEqual(result, { state: 'waiting-for-input' });
|
|
15
|
+
});
|
|
16
|
+
it('transitions to waiting-for-input on greeting', () => {
|
|
17
|
+
const parser = new ClaudeOutputParser();
|
|
18
|
+
const result = parser.onData('How can I help you today?', []);
|
|
19
|
+
assert.deepEqual(result, { state: 'waiting-for-input' });
|
|
20
|
+
});
|
|
21
|
+
it('transitions to processing after first prompt when output arrives', () => {
|
|
22
|
+
const parser = new ClaudeOutputParser();
|
|
23
|
+
// First: see the prompt
|
|
24
|
+
parser.onData('>\n', []);
|
|
25
|
+
// Then: output starts
|
|
26
|
+
const result = parser.onData('I will help you with that task...', []);
|
|
27
|
+
assert.deepEqual(result, { state: 'processing' });
|
|
28
|
+
});
|
|
29
|
+
it('transitions back to waiting-for-input after processing', () => {
|
|
30
|
+
const parser = new ClaudeOutputParser();
|
|
31
|
+
parser.onData('>\n', []);
|
|
32
|
+
parser.onData('Working on it...', []);
|
|
33
|
+
const result = parser.onData('>\n', []);
|
|
34
|
+
assert.deepEqual(result, { state: 'waiting-for-input' });
|
|
35
|
+
});
|
|
36
|
+
it('detects permission prompt', () => {
|
|
37
|
+
const parser = new ClaudeOutputParser();
|
|
38
|
+
parser.onData('>\n', []);
|
|
39
|
+
const result = parser.onData('Allow tool access to /usr/bin? Allow / Deny', []);
|
|
40
|
+
assert.deepEqual(result, { state: 'permission-prompt' });
|
|
41
|
+
});
|
|
42
|
+
it('detects error state', () => {
|
|
43
|
+
const parser = new ClaudeOutputParser();
|
|
44
|
+
parser.onData('>\n', []);
|
|
45
|
+
const result = parser.onData('Error: something went wrong', []);
|
|
46
|
+
assert.deepEqual(result, { state: 'error' });
|
|
47
|
+
});
|
|
48
|
+
it('ignores pure ANSI escape sequences', () => {
|
|
49
|
+
const parser = new ClaudeOutputParser();
|
|
50
|
+
const result = parser.onData('\x1b[32m\x1b[0m', []);
|
|
51
|
+
assert.equal(result, null);
|
|
52
|
+
});
|
|
53
|
+
it('returns null when state does not change', () => {
|
|
54
|
+
const parser = new ClaudeOutputParser();
|
|
55
|
+
parser.onData('>\n', []);
|
|
56
|
+
// Already in waiting-for-input, send another prompt
|
|
57
|
+
const result = parser.onData('>\n', []);
|
|
58
|
+
assert.equal(result, null);
|
|
59
|
+
});
|
|
60
|
+
it('reset returns to initializing', () => {
|
|
61
|
+
const parser = new ClaudeOutputParser();
|
|
62
|
+
parser.onData('>\n', []);
|
|
63
|
+
assert.equal(parser.state, 'waiting-for-input');
|
|
64
|
+
parser.reset();
|
|
65
|
+
assert.equal(parser.state, 'initializing');
|
|
66
|
+
});
|
|
67
|
+
it('stays initializing before first prompt', () => {
|
|
68
|
+
const parser = new ClaudeOutputParser();
|
|
69
|
+
const result = parser.onData('Loading configuration...', []);
|
|
70
|
+
// Still initializing since no prompt seen
|
|
71
|
+
assert.equal(result, null); // already in initializing, no change
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('CodexOutputParser', () => {
|
|
75
|
+
it('always returns null', () => {
|
|
76
|
+
const parser = new CodexOutputParser();
|
|
77
|
+
assert.equal(parser.onData('any output', []), null);
|
|
78
|
+
assert.equal(parser.onData('>\n', []), null);
|
|
79
|
+
assert.equal(parser.onData('Error: something', []), null);
|
|
80
|
+
});
|
|
81
|
+
it('reset is a no-op', () => {
|
|
82
|
+
const parser = new CodexOutputParser();
|
|
83
|
+
parser.reset(); // should not throw
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('outputParsers registry', () => {
|
|
87
|
+
it('creates ClaudeOutputParser for claude', () => {
|
|
88
|
+
const parser = outputParsers['claude']();
|
|
89
|
+
assert.ok(parser instanceof ClaudeOutputParser);
|
|
90
|
+
});
|
|
91
|
+
it('creates CodexOutputParser for codex', () => {
|
|
92
|
+
const parser = outputParsers['codex']();
|
|
93
|
+
assert.ok(parser instanceof CodexOutputParser);
|
|
94
|
+
});
|
|
95
|
+
});
|