conductor-remote 1.17.0 → 1.19.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.
@@ -0,0 +1 @@
1
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, -apple-system, "SF Pro Text", "Segoe UI", Roboto, sans-serif;--font-mono:ui-monospace, "SF Mono", "JetBrains Mono", "Fira Code", Menlo, monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--leading-tight:1.25;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--ease-out:cubic-bezier(0, 0, .2, 1);--animate-spin:spin 1s linear infinite;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-bg:#0a0b0e;--color-surface:#14161b;--color-surface-2:#1b1e26;--color-border:#262a33;--color-border-soft:#1e222a;--color-text:#e7e9ee;--color-muted:#8b909d;--color-faint:#5c616d;--color-accent:#8b7dff;--color-accent-soft:#2a2650;--color-working:#f5a623;--color-idle:#3ecf8e;--color-done:#5b9dff;--color-pr-merged:#ac47ff;--color-pr-draft:#a4a3a2;--color-pr-conflicts:#fd9a00;--color-pr-mergeable:#49de80;--color-add:#3ecf8e;--color-del:#ff6b6b}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{-webkit-tap-highlight-color:transparent}html,body,#root{height:100dvh}html{background:var(--color-bg)}body{background:var(--color-bg);color:var(--color-text);font-family:var(--font-sans);-webkit-font-smoothing:antialiased;overscroll-behavior:none;margin:0;overflow-x:hidden}button{font:inherit;color:inherit;cursor:pointer}::-webkit-scrollbar{width:0;height:0}}@layer components{.card{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-border);background-color:var(--color-surface);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3.5);text-align:left;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}.card:active{background-color:var(--color-surface-2);scale:.985}.pill{padding-inline:calc(var(--spacing) * 3.5);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-muted);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px}.pill-active{background-color:var(--color-surface-2);color:var(--color-text)}.dot{width:calc(var(--spacing) * 2.5);height:calc(var(--spacing) * 2.5);background-color:var(--color-faint);--pulse-color:var(--color-working);border-radius:3.40282e38px;flex-shrink:0}.dot-pulse{box-shadow:0 0 0 0 var(--pulse-color)}@supports (color:color-mix(in lab,red,red)){.dot-pulse{box-shadow:0 0 color-mix(in srgb,var(--pulse-color) 60%,transparent)}}.dot-pulse{animation:1.6s ease-out infinite pulse}.dot-working{background:var(--color-working);box-shadow:0 0 #f5a62399}@supports (color:color-mix(in lab,red,red)){.dot-working{box-shadow:0 0 color-mix(in srgb,var(--color-working) 60%,transparent)}}.dot-working{animation:1.6s ease-out infinite pulse}.dot-idle{background:var(--color-idle)}.dot-done{background:var(--color-done)}.typing-dot{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5);background-color:var(--color-muted);border-radius:3.40282e38px;animation:1.2s ease-in-out infinite typing}.typing-dot:nth-child(2){animation-delay:.15s}.typing-dot:nth-child(3){animation-delay:.3s}.md>:first-child{margin-top:0}.md>:last-child{margin-bottom:0}.md p{margin:.5em 0}.md h1,.md h2,.md h3,.md h4,.md h5,.md h6{margin:.9em 0 .4em;font-weight:600;line-height:1.3}.md h1{font-size:1.25em}.md h2{font-size:1.15em}.md h3{font-size:1.05em}.md ul,.md ol{margin:.5em 0;padding-left:1.4em}.md ul{list-style:outside}.md ol{list-style:decimal}.md li,.md li>ul,.md li>ol{margin:.2em 0}.md code{background:#1b1e26cc;border-radius:.3rem}@supports (color:color-mix(in lab,red,red)){.md code{background:color-mix(in srgb,var(--color-surface-2) 80%,transparent)}}.md code{font-family:var(--font-mono);padding:.1em .35em;font-size:.85em}.md pre{border:1px solid var(--color-border-soft);background:var(--color-bg);border-radius:.6rem;margin:.5em 0;padding:.6em .75em;overflow-x:auto}.md pre code{background:0 0;padding:0;font-size:12px;line-height:1.5}.md a{color:var(--color-accent);text-underline-offset:2px;text-decoration:underline}.md blockquote{border-left:2px solid var(--color-border);color:var(--color-muted);margin:.5em 0;padding-left:.75em}.md hr{border:0;border-top:1px solid var(--color-border-soft);margin:.75em 0}.md table{border-collapse:collapse;max-width:100%;margin:.5em 0;font-size:.9em;display:block;overflow-x:auto}.md th,.md td{border:1px solid var(--color-border);text-align:left;padding:.3em .6em}.md th{background:var(--color-surface-2);font-weight:600}.md strong{font-weight:600}.md input[type=checkbox]{margin-right:.4em}}@layer utilities{.pointer-events-none{pointer-events:none}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.inset-x-0{inset-inline:0}.inset-y-0{inset-block:0}.top-0{top:0}.top-1\.5{top:calc(var(--spacing) * 1.5)}.top-full{top:100%}.-right-0\.5{right:calc(var(--spacing) * -.5)}.right-1\.5{right:calc(var(--spacing) * 1.5)}.right-2{right:calc(var(--spacing) * 2)}.-bottom-0\.5{bottom:calc(var(--spacing) * -.5)}.bottom-0{bottom:0}.left-0{left:0}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[60\]{z-index:60}.mx-auto{margin-inline:auto}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.-mr-1{margin-right:calc(var(--spacing) * -1)}.mr-1{margin-right:var(--spacing)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:var(--spacing)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.-ml-1{margin-left:calc(var(--spacing) * -1)}.ml-auto{margin-left:auto}.line-clamp-4{-webkit-line-clamp:4;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.size-1\.5\!{width:calc(var(--spacing) * 1.5)!important;height:calc(var(--spacing) * 1.5)!important}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.size-20{width:calc(var(--spacing) * 20);height:calc(var(--spacing) * 20)}.size-60{width:calc(var(--spacing) * 60);height:calc(var(--spacing) * 60)}.size-full{width:100%;height:100%}.h-full{height:100%}.max-h-40{max-height:calc(var(--spacing) * 40)}.w-1{width:var(--spacing)}.w-64{width:calc(var(--spacing) * 64)}.w-\[85\%\]{width:85%}.w-full{width:100%}.max-w-36{max-width:calc(var(--spacing) * 36)}.max-w-80{max-width:calc(var(--spacing) * 80)}.max-w-\[70vw\]{max-width:70vw}.max-w-\[85\%\]{max-width:85%}.max-w-\[92\%\]{max-width:92%}.max-w-full{max-width:100%}.max-w-sm{max-width:var(--container-sm)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:0}.min-w-5{min-width:calc(var(--spacing) * 5)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-0{--tw-translate-x:0;translate:var(--tw-translate-x) var(--tw-translate-y)}.-rotate-90{rotate:-90deg}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.list-none{list-style-type:none}.flex-col{flex-direction:column}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.self-start{align-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-t-3xl{border-top-left-radius:var(--radius-3xl);border-top-right-radius:var(--radius-3xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-accent\/50{border-color:#8b7dff80}@supports (color:color-mix(in lab,red,red)){.border-accent\/50{border-color:color-mix(in oklab,var(--color-accent) 50%,transparent)}}.border-border{border-color:var(--color-border)}.border-border-soft{border-color:var(--color-border-soft)}.border-del\/30{border-color:#ff6b6b4d}@supports (color:color-mix(in lab,red,red)){.border-del\/30{border-color:color-mix(in oklab,var(--color-del) 30%,transparent)}}.border-faint{border-color:var(--color-faint)}.border-white\/80{border-color:#fffc}@supports (color:color-mix(in lab,red,red)){.border-white\/80{border-color:color-mix(in oklab,var(--color-white) 80%,transparent)}}.border-t-accent{border-top-color:var(--color-accent)}.bg-accent{background-color:var(--color-accent)}.bg-accent-soft{background-color:var(--color-accent-soft)}.bg-add{background-color:var(--color-add)}.bg-add\/10{background-color:#3ecf8e1a}@supports (color:color-mix(in lab,red,red)){.bg-add\/10{background-color:color-mix(in oklab,var(--color-add) 10%,transparent)}}.bg-bg{background-color:var(--color-bg)}.bg-bg\/80{background-color:#0a0b0ecc}@supports (color:color-mix(in lab,red,red)){.bg-bg\/80{background-color:color-mix(in oklab,var(--color-bg) 80%,transparent)}}.bg-black{background-color:var(--color-black)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black) 60%,transparent)}}.bg-black\/75{background-color:#000000bf}@supports (color:color-mix(in lab,red,red)){.bg-black\/75{background-color:color-mix(in oklab,var(--color-black) 75%,transparent)}}.bg-del\/5{background-color:#ff6b6b0d}@supports (color:color-mix(in lab,red,red)){.bg-del\/5{background-color:color-mix(in oklab,var(--color-del) 5%,transparent)}}.bg-del\/10{background-color:#ff6b6b1a}@supports (color:color-mix(in lab,red,red)){.bg-del\/10{background-color:color-mix(in oklab,var(--color-del) 10%,transparent)}}.bg-idle\/10{background-color:#3ecf8e1a}@supports (color:color-mix(in lab,red,red)){.bg-idle\/10{background-color:color-mix(in oklab,var(--color-idle) 10%,transparent)}}.bg-surface{background-color:var(--color-surface)}.bg-surface-2{background-color:var(--color-surface-2)}.bg-surface\/60{background-color:#14161b99}@supports (color:color-mix(in lab,red,red)){.bg-surface\/60{background-color:color-mix(in oklab,var(--color-surface) 60%,transparent)}}.bg-surface\/95{background-color:#14161bf2}@supports (color:color-mix(in lab,red,red)){.bg-surface\/95{background-color:color-mix(in oklab,var(--color-surface) 95%,transparent)}}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-working{background-color:var(--color-working)}.bg-working\/10{background-color:#f5a6231a}@supports (color:color-mix(in lab,red,red)){.bg-working\/10{background-color:color-mix(in oklab,var(--color-working) 10%,transparent)}}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-16{padding-block:calc(var(--spacing) * 16)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-2\.5{padding-bottom:calc(var(--spacing) * 2.5)}.pl-2{padding-left:calc(var(--spacing) * 2)}.pl-3{padding-left:calc(var(--spacing) * 3)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11\.5px\]{font-size:11.5px}.text-\[11px\]{font-size:11px}.text-\[12\.5px\]{font-size:12.5px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.leading-\[1\.5\]{--tw-leading:1.5;line-height:1.5}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.\[overflow-wrap\:anywhere\]{overflow-wrap:anywhere}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent{color:var(--color-accent)}.text-add{color:var(--color-add)}.text-black{color:var(--color-black)}.text-del{color:var(--color-del)}.text-del\/80{color:#ff6b6bcc}@supports (color:color-mix(in lab,red,red)){.text-del\/80{color:color-mix(in oklab,var(--color-del) 80%,transparent)}}.text-faint{color:var(--color-faint)}.text-idle{color:var(--color-idle)}.text-muted{color:var(--color-muted)}.text-text{color:var(--color-text)}.text-white{color:var(--color-white)}.text-working{color:var(--color-working)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline-offset-2{text-underline-offset:2px}.\[color-scheme\:dark\]{color-scheme:dark}.opacity-60{opacity:.6}.shadow-\[0_0_0_100vmax_rgba\(0\,0\,0\,0\.5\)\]{--tw-shadow:0 0 0 100vmax var(--tw-shadow-color,#00000080);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-surface{--tw-ring-color:var(--color-surface)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-xl{--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.group-open\:rotate-90:is(:where(.group):is([open],:popover-open,:open) *){rotate:90deg}.placeholder\:font-sans::placeholder{font-family:var(--font-sans)}.placeholder\:text-faint::placeholder{color:var(--color-faint)}.focus-within\:border-accent\/60:focus-within{border-color:#8b7dff99}@supports (color:color-mix(in lab,red,red)){.focus-within\:border-accent\/60:focus-within{border-color:color-mix(in oklab,var(--color-accent) 60%,transparent)}}@media(hover:hover){.hover\:underline:hover{text-decoration-line:underline}}.active\:scale-90:active{--tw-scale-x:90%;--tw-scale-y:90%;--tw-scale-z:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.active\:bg-black\/70:active{background-color:#000000b3}@supports (color:color-mix(in lab,red,red)){.active\:bg-black\/70:active{background-color:color-mix(in oklab,var(--color-black) 70%,transparent)}}.active\:bg-surface:active{background-color:var(--color-surface)}.active\:bg-surface-2:active,.disabled\:bg-surface-2:disabled{background-color:var(--color-surface-2)}.disabled\:text-faint:disabled{color:var(--color-faint)}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-60:disabled{opacity:.6}@media(min-width:48rem){.md\:static{position:static}.md\:inset-0{inset:0}.md\:z-auto{z-index:auto}.md\:m-auto{margin:auto}.md\:block{display:block}.md\:hidden{display:none}.md\:h-fit{height:fit-content}.md\:w-72{width:calc(var(--spacing) * 72)}.md\:max-w-none{max-width:none}.md\:shrink-0{flex-shrink:0}.md\:translate-x-0{--tw-translate-x:0;translate:var(--tw-translate-x) var(--tw-translate-y)}.md\:rounded-3xl{border-radius:var(--radius-3xl)}.md\:transition-none{transition-property:none}}@media(min-width:64rem){.lg\:static{position:static}.lg\:z-auto{z-index:auto}.lg\:w-80{width:calc(var(--spacing) * 80)}.lg\:w-\[380px\]{width:380px}.lg\:shrink-0{flex-shrink:0}.lg\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.lg\:border-border-soft{border-color:var(--color-border-soft)}}@media(min-width:80rem){.xl\:w-\[460px\]{width:460px}}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}}@keyframes pulse{50%{opacity:.5}}@keyframes typing{0%,60%,to{opacity:.35;transform:none}30%{opacity:1;transform:translateY(-3px)}}@keyframes fade-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.fade-in{animation:.18s ease-out fade-in}.pt-safe{padding-top:max(env(safe-area-inset-top),.5rem)}.pb-safe{padding-bottom:max(env(safe-area-inset-bottom),.5rem)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(360deg)}}
package/dist/index.html CHANGED
@@ -11,8 +11,8 @@
11
11
  <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
12
12
  <link rel="icon" type="image/svg+xml" href="/icon.svg" />
13
13
  <title>Conductor Remote</title>
14
- <script type="module" crossorigin src="/assets/index-CFyDd-9V.js"></script>
15
- <link rel="stylesheet" crossorigin href="/assets/index-CFZawBc8.css">
14
+ <script type="module" crossorigin src="/assets/index-BCEPceVq.js"></script>
15
+ <link rel="stylesheet" crossorigin href="/assets/index-CZuhTf-G.css">
16
16
  <link rel="manifest" href="/manifest.webmanifest"></head>
17
17
  <body>
18
18
  <div id="root"></div>
package/dist/sw.js CHANGED
@@ -1 +1 @@
1
- if(!self.define){let e,i={};const n=(n,s)=>(n=new URL(n+".js",s).href,i[n]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=i,document.head.appendChild(e)}else e=n,importScripts(n),i()}).then(()=>{let e=i[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(s,o)=>{const r=e||("document"in self?document.currentScript.src:"")||location.href;if(i[r])return;let c={};const t=e=>n(e,r),l={module:{uri:r},exports:c,require:t};i[r]=Promise.all(s.map(e=>l[e]||t(e))).then(e=>(o(...e),c))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"index.html",revision:"8cad4855e591d94059cbf97bab27d3f0"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-CFyDd-9V.js",revision:null},{url:"assets/index-CFZawBc8.css",revision:null},{url:"apple-touch-icon.png",revision:"2b9301416b880d45d4bb655f2600d1f2"},{url:"icon-192.png",revision:"c5e01ac58768627e18ee7b8b6a9239ef"},{url:"icon-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon-maskable-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon.svg",revision:"c1aee186821798733dd477e69a0ef243"},{url:"manifest.webmanifest",revision:"cf88fbc5755108a7fe0616fa160a8a15"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api\//]}))});
1
+ if(!self.define){let e,i={};const n=(n,s)=>(n=new URL(n+".js",s).href,i[n]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=i,document.head.appendChild(e)}else e=n,importScripts(n),i()}).then(()=>{let e=i[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(s,o)=>{const r=e||("document"in self?document.currentScript.src:"")||location.href;if(i[r])return;let t={};const c=e=>n(e,r),l={module:{uri:r},exports:t,require:c};i[r]=Promise.all(s.map(e=>l[e]||c(e))).then(e=>(o(...e),t))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"index.html",revision:"96338721bbb009f99c32378af0855187"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-CZuhTf-G.css",revision:null},{url:"assets/index-BCEPceVq.js",revision:null},{url:"apple-touch-icon.png",revision:"2b9301416b880d45d4bb655f2600d1f2"},{url:"icon-192.png",revision:"c5e01ac58768627e18ee7b8b6a9239ef"},{url:"icon-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon-maskable-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon.svg",revision:"c1aee186821798733dd477e69a0ef243"},{url:"manifest.webmanifest",revision:"cf88fbc5755108a7fe0616fa160a8a15"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api\//]}))});
@@ -94,5 +94,25 @@ export async function workspaceDiff(worktree, base) {
94
94
  const truncated = patch.length > MAX_PATCH_BYTES;
95
95
  if (truncated)
96
96
  patch = `${patch.slice(0, MAX_PATCH_BYTES)}\n\n… diff truncated (${patch.length} bytes) …`;
97
- return { base: ref, mergeBase, files, patch, truncated };
97
+ const { dirty, unpushed } = await localState(worktree);
98
+ return { base: ref, mergeBase, files, patch, truncated, dirty, unpushed };
99
+ }
100
+ /**
101
+ * Local publish state of the worktree: does it have uncommitted changes, and are
102
+ * there commits not yet on its remote-tracking branch? Together these drive the
103
+ * bar's "Commit & push" action (you must land local work before a PR reflects it).
104
+ */
105
+ async function localState(worktree) {
106
+ const status = await git(worktree, ['status', '--porcelain']).catch(() => '');
107
+ const dirty = status.trim() !== '';
108
+ let unpushed = false;
109
+ try {
110
+ // `@{upstream}` throws when no upstream is configured — then there's nothing to compare against.
111
+ const count = (await git(worktree, ['rev-list', '--count', '@{upstream}..HEAD'])).trim();
112
+ unpushed = Number(count) > 0;
113
+ }
114
+ catch {
115
+ unpushed = false;
116
+ }
117
+ return { dirty, unpushed };
98
118
  }
@@ -1,115 +1,53 @@
1
1
  import { execFile } from 'node:child_process';
2
2
  import { promisify } from 'node:util';
3
3
  const exec = promisify(execFile);
4
- async function git(cwd, args) {
5
- const { stdout } = await exec('git', ['-C', cwd, ...args], {
6
- encoding: 'utf8',
7
- maxBuffer: 8 * 1024 * 1024,
8
- timeout: 20_000
9
- });
4
+ async function gh(root, args) {
5
+ const { stdout } = await exec('gh', args, { cwd: root, encoding: 'utf8', timeout: 30_000 });
10
6
  return stdout;
11
7
  }
12
- /** Count of uncommitted (tracked + untracked) entries in a checkout. */
13
- async function countDirty(cwd) {
14
- const out = await git(cwd, ['status', '--porcelain']).catch(() => '');
15
- return out.split('\n').filter(Boolean).length;
16
- }
17
- /** Commits on `branch` not reachable from `base`. */
18
- async function countAhead(root, base, branch) {
19
- const out = await git(root, ['rev-list', '--count', `${base}..${branch}`]).catch(() => '0');
20
- const n = Number(out.trim());
21
- return Number.isFinite(n) ? n : 0;
22
- }
23
8
  /**
24
- * Would `git merge <branch>` into `base` conflict? `merge-tree --write-tree`
25
- * merges in memory (touches nothing) and exits 1 on conflict, 0 when clean. Any
26
- * other failure → treat as non-conflicting (the real merge will surface it).
9
+ * The repo's merge method, preferring squash (this project's convention), then a
10
+ * merge commit, then rebase whichever GitHub allows for the repo. Defaults to
11
+ * squash if the query fails.
27
12
  */
28
- async function wouldConflict(root, base, branch) {
13
+ async function preferredMethod(root) {
29
14
  try {
30
- await git(root, ['merge-tree', '--write-tree', base, branch]);
31
- return false;
15
+ const out = await gh(root, ['repo', 'view', '--json', 'squashMergeAllowed,mergeCommitAllowed,rebaseMergeAllowed']);
16
+ const a = JSON.parse(out);
17
+ if (a.squashMergeAllowed)
18
+ return 'squash';
19
+ if (a.mergeCommitAllowed)
20
+ return 'merge';
21
+ if (a.rebaseMergeAllowed)
22
+ return 'rebase';
32
23
  }
33
- catch (err) {
34
- return err.code === 1;
24
+ catch {
25
+ // gh missing / not a GitHub repo → fall back; the merge call will surface any real error.
35
26
  }
27
+ return 'squash';
36
28
  }
37
29
  /**
38
- * Everything the POST needs to know before merging also served to the PWA so
39
- * the confirm sheet can show the target, commit count, and any blocker up front.
30
+ * Merge the workspace's open PR. `gh pr merge <branch>` resolves the PR from its
31
+ * head branch, so we need no PR number. We don't `--delete-branch` (the worktree
32
+ * still has it checked out; Conductor deletes it on archive, not on merge) and
33
+ * push nothing — GitHub performs the merge. Any GitHub-side refusal (checks,
34
+ * reviews, conflicts, draft) comes back as a loud error.
40
35
  */
41
- export async function mergePrecheck(ws) {
36
+ export async function mergePr(ws) {
42
37
  const branch = ws.branch ?? '';
43
- const base = ws.baseBranch;
44
38
  const root = ws.repo_root;
45
- const block = (reason, detail, extra = {}) => ({
46
- base,
47
- branch,
48
- canMerge: false,
49
- reason,
50
- detail,
51
- ahead: 0,
52
- uncommitted: 0,
53
- ...extra
54
- });
55
39
  if (!branch)
56
- return block('no-branch', 'workspace has no branch');
40
+ return { ok: false, branch, error: 'workspace has no branch' };
57
41
  if (!root)
58
- return block('no-repo', 'repo root unresolved');
59
- const uncommitted = ws.worktree ? await countDirty(ws.worktree) : 0;
60
- // The primary checkout must be on the base branch — we never switch Conductor's
61
- // checkout out from under it; refuse and tell the user to check it out instead.
62
- const head = (await git(root, ['rev-parse', '--abbrev-ref', 'HEAD']).catch(() => '')).trim();
63
- if (head && head !== base)
64
- return block('not-on-base', `the ${ws.repo_name ?? 'repo'} checkout is on '${head}', not '${base}'`, {
65
- uncommitted,
66
- headBranch: head
67
- });
68
- if (await countDirty(root))
69
- return block('dirty-base', `the '${base}' checkout has uncommitted changes`, { uncommitted });
70
- const ahead = await countAhead(root, base, branch);
71
- if (ahead === 0)
72
- return block('nothing-to-merge', `nothing new to merge into '${base}'`, { uncommitted });
73
- if (await wouldConflict(root, base, branch))
74
- return {
75
- base,
76
- branch,
77
- canMerge: false,
78
- reason: 'conflicts',
79
- detail: `merging into '${base}' would conflict`,
80
- ahead,
81
- uncommitted
82
- };
83
- return { base, branch, canMerge: true, reason: 'ok', ahead, uncommitted };
84
- }
85
- /**
86
- * Perform the merge, faithful to Conductor: a local `git merge --no-edit <branch>`
87
- * into the base branch in the primary checkout. Re-runs the precheck as the
88
- * authoritative gate (the PWA's copy is advisory and may be stale), and aborts a
89
- * conflicting merge so the checkout is never left half-merged.
90
- */
91
- export async function mergeWorkspace(ws) {
92
- const pre = await mergePrecheck(ws);
93
- if (!pre.canMerge)
94
- return { ok: false, base: pre.base, branch: pre.branch, error: pre.detail, reason: pre.reason };
95
- const root = ws.repo_root;
42
+ return { ok: false, branch, error: 'repo root unresolved' };
43
+ const method = await preferredMethod(root);
96
44
  try {
97
- const summary = await git(root, ['merge', '--no-edit', pre.branch]);
98
- return { ok: true, base: pre.base, branch: pre.branch, summary: summary.trim() };
45
+ await gh(root, ['pr', 'merge', branch, `--${method}`]);
46
+ return { ok: true, branch, method };
99
47
  }
100
48
  catch (err) {
101
49
  const stderr = err.stderr;
102
50
  const message = (stderr || (err instanceof Error ? err.message : String(err))).trim();
103
- const conflicted = /conflict/i.test(message);
104
- // Leave the primary checkout clean rather than stuck mid-merge.
105
- if (conflicted)
106
- await git(root, ['merge', '--abort']).catch(() => undefined);
107
- return {
108
- ok: false,
109
- base: pre.base,
110
- branch: pre.branch,
111
- error: message,
112
- reason: conflicted ? 'conflicts' : 'error'
113
- };
51
+ return { ok: false, branch, method, error: message };
114
52
  }
115
53
  }
@@ -19,12 +19,16 @@ export function attachPrStatus(workspaces) {
19
19
  const repos = new Set();
20
20
  for (const w of workspaces) {
21
21
  w.pr_status = null;
22
+ w.pr_number = null;
23
+ w.pr_url = null;
22
24
  if (!w.branch || !w.repo_root)
23
25
  continue;
24
26
  repos.add(w.repo_root);
25
27
  const pr = repoCache.get(w.repo_root)?.prs?.get(w.branch);
26
28
  if (!pr)
27
29
  continue;
30
+ w.pr_number = pr.number;
31
+ w.pr_url = pr.url;
28
32
  if (pr.state === 'MERGED')
29
33
  w.pr_status = 'merged';
30
34
  else if (pr.state === 'OPEN') {
@@ -57,7 +61,7 @@ function refreshRepo(root) {
57
61
  /** One `gh` call per repo → its PRs keyed by head branch. Never rejects. */
58
62
  async function fetchRepoPRs(root) {
59
63
  try {
60
- const { stdout } = await exec('gh', ['pr', 'list', '--state', 'all', '--limit', '100', '--json', 'headRefName,state,isDraft,updatedAt'], { cwd: root, encoding: 'utf8', timeout: 15_000 });
64
+ const { stdout } = await exec('gh', ['pr', 'list', '--state', 'all', '--limit', '100', '--json', 'headRefName,number,url,state,isDraft,updatedAt'], { cwd: root, encoding: 'utf8', timeout: 15_000 });
61
65
  const list = JSON.parse(stdout);
62
66
  // Oldest first so the newest PR wins when a branch has been reused across PRs.
63
67
  list.sort((a, b) => a.updatedAt.localeCompare(b.updatedAt));
@@ -90,19 +90,36 @@ export class Reads {
90
90
  WHERE workspace_id = ? AND COALESCE(is_hidden, 0) = 0
91
91
  ORDER BY created_at ASC`, [workspaceId]);
92
92
  }
93
+ /** Session → worktree path, cached: it's stable for a session's lifetime and polled every tick. */
94
+ worktreeBySession = new Map();
95
+ sessionWorktree(sessionId) {
96
+ const cached = this.worktreeBySession.get(sessionId);
97
+ if (cached !== undefined)
98
+ return cached;
99
+ const rows = this.db.query(`SELECT w.directory_name, w.branch, r.name AS repo_name, r.root_path AS repo_root
100
+ FROM sessions s
101
+ JOIN workspaces w ON w.id = s.workspace_id
102
+ LEFT JOIN repos r ON r.id = w.repository_id
103
+ WHERE s.id = ? LIMIT 1`, [sessionId]);
104
+ const r = rows[0];
105
+ const worktree = r
106
+ ? resolveWorktree(this.workspacesRoot, r.repo_name, r.directory_name, r.branch, r.repo_root)
107
+ : null;
108
+ this.worktreeBySession.set(sessionId, worktree);
109
+ return worktree;
110
+ }
93
111
  /** Incremental transcript fetch. `afterRowid` is the cursor from a prior call. */
94
112
  getMessages(sessionId, afterRowid = 0) {
95
113
  const rows = this.db.query(`SELECT rowid, id, role, content, full_message, created_at, sent_at, queue_order
96
114
  FROM session_messages
97
115
  WHERE session_id = ? AND rowid > ?
98
116
  ORDER BY rowid ASC`, [sessionId, afterRowid]);
117
+ const worktree = this.sessionWorktree(sessionId);
99
118
  const entries = [];
100
119
  let cursor = afterRowid;
101
120
  for (const row of rows) {
102
121
  cursor = row.rowid;
103
- const entry = parseMessage(row);
104
- if (entry)
105
- entries.push(entry);
122
+ entries.push(...parseMessage(row, worktree));
106
123
  }
107
124
  return { entries, cursor };
108
125
  }
@@ -7,7 +7,7 @@ import { startAutoUpdate, updateStatus } from "./autoupdate.js";
7
7
  import { loadConfig } from "./config.js";
8
8
  import { ConductorDb } from "./db.js";
9
9
  import { workspaceDiff } from "./git.js";
10
- import { mergePrecheck, mergeWorkspace } from "./merge.js";
10
+ import { mergePr } from "./merge.js";
11
11
  import { attachPrStatus } from "./pr.js";
12
12
  import { Reads } from "./reads.js";
13
13
  import { describeActuator, newChat, pickActuator } from "./writes.js";
@@ -82,9 +82,12 @@ function cacheControl(rel) {
82
82
  }
83
83
  function serveStatic(_req, res, pathname) {
84
84
  const rel = pathname === '/' ? 'index.html' : pathname.replace(/^\/+/, '');
85
- const filePath = path.join(cfg.publicDir, rel);
86
- // Contain to publicDir.
87
- if (!filePath.startsWith(cfg.publicDir)) {
85
+ const filePath = path.resolve(cfg.publicDir, rel);
86
+ // Contain to publicDir. The URL parser already collapses `..`/`%2e%2e` dot-segments, but don't lean on
87
+ // that: reject anything that resolves outside the dir (a bare `startsWith` would also admit a sibling
88
+ // like `dist-node/`). An empty relative (filePath === publicDir) falls through to the SPA shell below.
89
+ const within = path.relative(cfg.publicDir, filePath);
90
+ if (within.startsWith('..') || path.isAbsolute(within)) {
88
91
  res.writeHead(403).end();
89
92
  return;
90
93
  }
@@ -173,16 +176,13 @@ const server = http.createServer(async (req, res) => {
173
176
  const diff = await workspaceDiff(ws.worktree, ws.baseBranch);
174
177
  return json(req, res, 200, diff);
175
178
  }
176
- // GET /api/workspaces/:id/merge — precheck: can this branch merge into its base?
177
- // POST /api/workspaces/:id/merge — do it (local `git merge`, mirroring Conductor’s merge button)
179
+ // POST /api/workspaces/:id/merge — merge the workspace's open PR (mirrors Conductor's merge button)
178
180
  m = pathname.match(/^\/api\/workspaces\/([^/]+)\/merge$/);
179
- if ((req.method === 'GET' || req.method === 'POST') && m) {
181
+ if (req.method === 'POST' && m) {
180
182
  const ws = reads.getWorkspace(decodeURIComponent(m[1]));
181
183
  if (!ws)
182
184
  return json(req, res, 404, { error: 'workspace not found' });
183
- if (req.method === 'GET')
184
- return json(req, res, 200, await mergePrecheck(ws));
185
- const result = await mergeWorkspace(ws);
185
+ const result = await mergePr(ws);
186
186
  return json(req, res, result.ok ? 200 : 409, result);
187
187
  }
188
188
  // GET /api/sessions/:id/messages?after=<rowid>
@@ -210,7 +210,9 @@ const server = http.createServer(async (req, res) => {
210
210
  return json(req, res, 404, { error: 'no route', pathname });
211
211
  }
212
212
  catch (err) {
213
- return json(req, res, 500, { error: err instanceof Error ? err.message : String(err) });
213
+ // Log the detail locally; don't reflect internals (paths, stack strings) back over the wire.
214
+ console.error(`[relay] ${req.method} ${pathname} failed:`, err);
215
+ return json(req, res, 500, { error: 'internal error' });
214
216
  }
215
217
  });
216
218
  server.listen(cfg.port, cfg.host, () => {
@@ -1,36 +1,40 @@
1
1
  /**
2
2
  * Conductor stores each turn's raw Claude Code SDK stream JSON in
3
3
  * `session_messages.content`. User-typed prompts are stored as plain text.
4
- * This turns a row into a compact, phone-renderable entry.
4
+ * This turns a row into compact, phone-renderable entries.
5
+ *
6
+ * Classification rule (verified against the whole DB): a JSON frame with
7
+ * `type:"user"` is ALWAYS tool plumbing — every one of them carries
8
+ * tool_result blocks, never the user's own words. Real prompts are the
9
+ * plain-text rows. Never render an SDK user frame as a user bubble.
5
10
  */
6
- function textFromBlocks(blocks) {
7
- const parts = [];
8
- let tool;
9
- for (const b of blocks) {
10
- if (b.type === 'text' && b.text)
11
- parts.push(b.text);
12
- else if (b.type === 'thinking' && typeof b.text === 'string')
13
- parts.push(b.text);
14
- else if (b.type === 'tool_use') {
15
- tool = b.name;
16
- const inputStr = summarizeToolInput(b.input);
17
- parts.push(inputStr ? `▸ ${b.name}: ${inputStr}` : `▸ ${b.name}`);
18
- }
19
- else if (b.type === 'tool_result') {
20
- parts.push(summarizeToolResult(b.content));
21
- }
22
- }
23
- return { text: parts.join('\n').trim(), tool };
11
+ const clip = (s, n) => (s.length > n ? `${s.slice(0, n)}…` : s);
12
+ const str = (v) => (typeof v === 'string' && v.trim() ? v.trim() : undefined);
13
+ /** Make tool details repo-relative: absolute worktree paths waste the whole line on a phone. */
14
+ function stripWorktree(s, worktree) {
15
+ if (!worktree)
16
+ return s;
17
+ // Conductor prefixes commands with `cd <worktree>` (newline- or &&-joined) drop the whole clause.
18
+ if (s.startsWith(`cd ${worktree}`))
19
+ s = s.slice(`cd ${worktree}`.length).replace(/^\s*(&&)?\s*/, '');
20
+ return s.replaceAll(`${worktree}/`, '').replaceAll(worktree, '.');
24
21
  }
25
- function summarizeToolInput(input) {
22
+ /**
23
+ * Mirror Conductor's one-line tool rows: the human description as the title
24
+ * (Bash always has one), the primary input as a mono detail. Tools without a
25
+ * recognizable primary input get the title alone — dumping raw JSON is noise.
26
+ */
27
+ function summarizeToolUse(name, input, worktree) {
26
28
  if (!input || typeof input !== 'object')
27
- return '';
29
+ return { text: name };
28
30
  const o = input;
29
- const key = o.command ?? o.file_path ?? o.path ?? o.pattern ?? o.description ?? o.prompt;
30
- const s = typeof key === 'string' ? key : JSON.stringify(o);
31
- return s.length > 140 ? `${s.slice(0, 140)}…` : s;
31
+ const text = str(o.description) ?? name;
32
+ const detail = str(o.command) ?? str(o.file_path) ?? str(o.path) ?? str(o.pattern) ?? str(o.url) ?? str(o.skill) ?? str(o.prompt);
33
+ if (!detail || detail === text)
34
+ return { text };
35
+ return { text, detail: clip(stripWorktree(detail, worktree).replace(/\s+/g, ' '), 160) };
32
36
  }
33
- function summarizeToolResult(content) {
37
+ function resultText(content) {
34
38
  let s = '';
35
39
  if (typeof content === 'string')
36
40
  s = content;
@@ -39,45 +43,66 @@ function summarizeToolResult(content) {
39
43
  .map(c => (c && typeof c === 'object' && 'text' in c ? String(c.text) : ''))
40
44
  .join('');
41
45
  }
42
- s = s.trim();
43
- if (!s)
44
- return '↳ (result)';
45
- return `↳ ${s.length > 200 ? `${s.slice(0, 200)}…` : s}`;
46
+ return clip(s.replace(/<\/?tool_use_error>/g, '').trim(), 400);
46
47
  }
47
- export function parseMessage(row) {
48
+ export function parseMessage(row, worktree = null) {
48
49
  const queued = row.queue_order !== null && row.sent_at === null;
49
- const base = { id: row.id, rowid: row.rowid, ts: row.created_at, queued };
50
+ const base = { rowid: row.rowid, ts: row.created_at, queued };
50
51
  const content = row.content ?? '';
51
- // Plain user prompt (not SDK JSON).
52
+ // Plain user prompt (not SDK JSON) — the only source of real user bubbles.
52
53
  if (!content.startsWith('{')) {
53
54
  if (!content.trim())
54
- return null;
55
- return { ...base, role: 'user', text: content };
55
+ return [];
56
+ return [{ ...base, id: row.id, role: 'user', text: content }];
56
57
  }
57
58
  let parsed;
58
59
  try {
59
60
  parsed = JSON.parse(content);
60
61
  }
61
62
  catch {
62
- return { ...base, role: 'system', text: content.slice(0, 200) };
63
+ return [{ ...base, id: row.id, role: 'system', text: clip(content, 200) }];
63
64
  }
64
- // Skip pure bookkeeping frames (token accounting, etc.).
65
- if (parsed.type === 'system' && parsed.subtype === 'thinking_tokens')
66
- return null;
67
- if (parsed.type === 'result')
68
- return null;
65
+ // Bookkeeping frames: hooks, init, token accounting, end-of-turn results.
66
+ if (parsed.type === 'system' || parsed.type === 'result')
67
+ return [];
69
68
  const blocks = parsed.message?.content;
70
- if (Array.isArray(blocks)) {
71
- const { text, tool } = textFromBlocks(blocks);
72
- if (!text)
73
- return null;
74
- if (tool)
75
- return { ...base, role: 'tool', text, tool };
76
- if (parsed.type === 'user')
77
- return { ...base, role: 'user', text };
78
- return { ...base, role: 'assistant', text };
69
+ if (!Array.isArray(blocks)) {
70
+ if (parsed.type === 'user' || parsed.type === 'assistant')
71
+ return [];
72
+ // Unknown frame shape — keep a dim raw dump so Conductor drift stays visible.
73
+ return [{ ...base, id: row.id, role: 'system', text: clip(content, 200) }];
74
+ }
75
+ const entries = [];
76
+ const push = (e) => entries.push({ ...base, ...e, id: `${row.id}:${entries.length}` });
77
+ let pending = [];
78
+ const flush = () => {
79
+ const text = pending.join('\n').trim();
80
+ if (text)
81
+ push({ role: 'assistant', text });
82
+ pending = [];
83
+ };
84
+ for (const b of blocks) {
85
+ if (b.type === 'text' && typeof b.text === 'string') {
86
+ // Text inside an SDK user frame would be injected context, not the user.
87
+ if (parsed.type !== 'user')
88
+ pending.push(b.text);
89
+ }
90
+ else if (b.type === 'thinking') {
91
+ flush();
92
+ const text = str(b.thinking) ?? str(b.text);
93
+ if (text)
94
+ push({ role: 'thinking', text });
95
+ }
96
+ else if (b.type === 'tool_use' && typeof b.name === 'string') {
97
+ flush();
98
+ push({ role: 'tool', tool: b.name, ...summarizeToolUse(b.name, b.input, worktree) });
99
+ }
100
+ else if (b.type === 'tool_result' && b.is_error) {
101
+ // Successful results are noise on a phone; surface only failures.
102
+ flush();
103
+ push({ role: 'tool', error: true, text: resultText(b.content) || '(tool error)' });
104
+ }
79
105
  }
80
- if (parsed.type === 'system')
81
- return null;
82
- return { ...base, role: 'system', text: content.slice(0, 200) };
106
+ flush();
107
+ return entries;
83
108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conductor-remote",
3
- "version": "1.17.0",
3
+ "version": "1.19.0",
4
4
  "type": "module",
5
5
  "packageManager": "yarn@4.15.0",
6
6
  "description": "Phone control panel for local Conductor agents. Reads ride SQLite + git; prompts ride Conductor's own dispatch path.",