claude-remote-cli 3.4.2 → 3.5.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.
- package/dist/frontend/assets/{index-Dn_qB0Yk.js → index-BSLqoOlv.js} +17 -17
- package/dist/frontend/assets/{index-m2Bcao6e.css → index-BtybMy6I.css} +1 -1
- package/dist/frontend/index.html +2 -2
- 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 +46 -12
- package/dist/test/output-parser.test.js +95 -0
- package/package.json +1 -1
|
@@ -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}.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}.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,.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}}/**
|
|
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-BSLqoOlv.js"></script>
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BtybMy6I.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="app"></div>
|
|
@@ -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,42 @@ 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 prompt = `Output ONLY a short kebab-case git branch name (no explanation, no backticks, no prefix, just the name) that describes this task:\n\n${firstMessage.slice(0, 500)}`;
|
|
45
|
+
const { stdout } = await execFileAsync('claude', ['-p', '--model', 'haiku', prompt], {
|
|
46
|
+
cwd: session.cwd,
|
|
47
|
+
timeout: 30000,
|
|
48
|
+
});
|
|
49
|
+
const branchName = stdout.trim().replace(/`/g, '').replace(/[^a-z0-9-]/gi, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').toLowerCase().slice(0, 60);
|
|
50
|
+
if (!branchName)
|
|
51
|
+
return;
|
|
52
|
+
await execFileAsync('git', ['branch', '-m', branchName], { cwd: session.cwd });
|
|
53
|
+
// Update session state
|
|
54
|
+
const displayName = branchToDisplayName(branchName);
|
|
55
|
+
session.branchName = branchName;
|
|
56
|
+
session.displayName = displayName;
|
|
57
|
+
broadcastEvent('session-renamed', {
|
|
58
|
+
sessionId: session.id,
|
|
59
|
+
branchName,
|
|
60
|
+
displayName,
|
|
61
|
+
});
|
|
62
|
+
if (cfgPath) {
|
|
63
|
+
writeMeta(cfgPath, {
|
|
64
|
+
worktreePath: session.repoPath,
|
|
65
|
+
displayName,
|
|
66
|
+
lastActivity: new Date().toISOString(),
|
|
67
|
+
branchName,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Sideband rename is best-effort — fall back to branch watcher if claude CLI isn't available
|
|
73
|
+
if (cfgPath)
|
|
74
|
+
startBranchWatcher(session, broadcastEvent, cfgPath);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
41
77
|
function parseCookies(cookieHeader) {
|
|
42
78
|
const cookies = {};
|
|
43
79
|
if (!cookieHeader)
|
|
@@ -143,29 +179,24 @@ function setupWebSocket(server, authenticatedTokens, watcher, configPath) {
|
|
|
143
179
|
}
|
|
144
180
|
}
|
|
145
181
|
catch (_) { }
|
|
146
|
-
//
|
|
182
|
+
// Sideband branch rename: capture first message, pass through unmodified, rename out-of-band
|
|
147
183
|
if (ptySession.needsBranchRename) {
|
|
148
184
|
if (!ptySession._renameBuffer)
|
|
149
185
|
ptySession._renameBuffer = '';
|
|
150
186
|
const enterIndex = str.indexOf('\r');
|
|
151
187
|
if (enterIndex === -1) {
|
|
152
188
|
ptySession._renameBuffer += str;
|
|
153
|
-
ptySession.pty.write(str); //
|
|
189
|
+
ptySession.pty.write(str); // pass through to PTY normally — user sees their typing
|
|
154
190
|
return;
|
|
155
191
|
}
|
|
156
|
-
// Enter detected —
|
|
192
|
+
// Enter detected — pass everything through unmodified
|
|
157
193
|
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);
|
|
194
|
+
const firstMessage = buffered + str.slice(0, enterIndex);
|
|
195
|
+
ptySession.pty.write(str); // pass through the Enter key
|
|
165
196
|
ptySession.needsBranchRename = false;
|
|
166
197
|
delete ptySession._renameBuffer;
|
|
167
|
-
|
|
168
|
-
|
|
198
|
+
// Sideband: spawn headless claude to generate branch name (async, non-blocking)
|
|
199
|
+
spawnBranchRename(ptySession, firstMessage, configPath, broadcastEvent);
|
|
169
200
|
return;
|
|
170
201
|
}
|
|
171
202
|
// Use ptySession.pty dynamically so writes go to current PTY
|
|
@@ -182,6 +213,9 @@ function setupWebSocket(server, authenticatedTokens, watcher, configPath) {
|
|
|
182
213
|
sessions.onIdleChange((sessionId, idle) => {
|
|
183
214
|
broadcastEvent('session-idle-changed', { sessionId, idle });
|
|
184
215
|
});
|
|
216
|
+
sessions.onStateChange((sessionId, state) => {
|
|
217
|
+
broadcastEvent('session-state-changed', { sessionId, state });
|
|
218
|
+
});
|
|
185
219
|
sessions.onSessionEnd((sessionId, repoPath, branchName) => {
|
|
186
220
|
broadcastEvent('session-ended', { sessionId, repoPath, branchName });
|
|
187
221
|
});
|
|
@@ -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
|
+
});
|