mirror of
https://github.com/stablyai/orca.git
synced 2026-10-16 00:03:09 +00:00
2194 lines
78 KiB
HTML
2194 lines
78 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Making Remote Hosts First Class in Orca</title>
|
||
<style>
|
||
:root {
|
||
color-scheme: dark;
|
||
--background: #111111;
|
||
--foreground: #f4f4f5;
|
||
--muted: #1c1c1f;
|
||
--muted-foreground: #a1a1aa;
|
||
--card: #161618;
|
||
--border: #2a2a2f;
|
||
--accent: #242429;
|
||
--ring: #d4d4d8;
|
||
--success: #7dd3a7;
|
||
--warning: #f4c56a;
|
||
--danger: #f08c8c;
|
||
--info: #8ab4f8;
|
||
}
|
||
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
margin: 0;
|
||
background: var(--background);
|
||
color: var(--foreground);
|
||
font-family:
|
||
Geist,
|
||
ui-sans-serif,
|
||
system-ui,
|
||
-apple-system,
|
||
BlinkMacSystemFont,
|
||
"Segoe UI",
|
||
sans-serif;
|
||
letter-spacing: 0.01em;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
main {
|
||
max-width: 1120px;
|
||
margin: 0 auto;
|
||
padding: 48px 24px 72px;
|
||
}
|
||
|
||
header {
|
||
border-bottom: 1px solid var(--border);
|
||
padding-bottom: 28px;
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.eyebrow {
|
||
color: var(--muted-foreground);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.05em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
h1 {
|
||
font-size: 36px;
|
||
line-height: 1.1;
|
||
margin: 10px 0 14px;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
h2 {
|
||
font-size: 22px;
|
||
margin: 36px 0 14px;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
h3 {
|
||
font-size: 15px;
|
||
margin: 22px 0 8px;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
p {
|
||
margin: 0 0 12px;
|
||
}
|
||
|
||
a {
|
||
color: var(--info);
|
||
}
|
||
|
||
code {
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
background: var(--muted);
|
||
color: var(--foreground);
|
||
font-family:
|
||
"SFMono-Regular",
|
||
Consolas,
|
||
"Liberation Mono",
|
||
monospace;
|
||
font-size: 0.92em;
|
||
padding: 1px 5px;
|
||
}
|
||
|
||
.summary {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 12px;
|
||
margin-top: 24px;
|
||
}
|
||
|
||
.panel {
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
}
|
||
|
||
.panel strong {
|
||
display: block;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.muted {
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.callout {
|
||
border: 1px solid var(--border);
|
||
border-left: 3px solid var(--info);
|
||
background: color-mix(in srgb, var(--info) 8%, var(--background));
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
margin: 18px 0;
|
||
}
|
||
|
||
.decision {
|
||
border-left-color: var(--success);
|
||
background: color-mix(in srgb, var(--success) 8%, var(--background));
|
||
}
|
||
|
||
.warning {
|
||
border-left-color: var(--warning);
|
||
background: color-mix(in srgb, var(--warning) 8%, var(--background));
|
||
}
|
||
|
||
.danger {
|
||
border-left-color: var(--danger);
|
||
background: color-mix(in srgb, var(--danger) 8%, var(--background));
|
||
}
|
||
|
||
ul,
|
||
ol {
|
||
margin: 0 0 16px 22px;
|
||
padding: 0;
|
||
}
|
||
|
||
li {
|
||
margin: 7px 0;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin: 16px 0 24px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
th,
|
||
td {
|
||
border-bottom: 1px solid var(--border);
|
||
padding: 10px 12px;
|
||
text-align: left;
|
||
vertical-align: top;
|
||
font-size: 14px;
|
||
}
|
||
|
||
th {
|
||
background: var(--muted);
|
||
color: var(--foreground);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
|
||
tr:last-child td {
|
||
border-bottom: 0;
|
||
}
|
||
|
||
.tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
border: 1px solid var(--border);
|
||
border-radius: 999px;
|
||
color: var(--muted-foreground);
|
||
font-size: 12px;
|
||
padding: 2px 8px;
|
||
margin: 0 6px 6px 0;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.phase {
|
||
display: grid;
|
||
grid-template-columns: 108px 1fr;
|
||
gap: 16px;
|
||
border-top: 1px solid var(--border);
|
||
padding: 18px 0;
|
||
}
|
||
|
||
.phase:first-of-type {
|
||
border-top: 0;
|
||
}
|
||
|
||
.phase-label {
|
||
color: var(--muted-foreground);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
|
||
.phase h3 {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.footer {
|
||
color: var(--muted-foreground);
|
||
border-top: 1px solid var(--border);
|
||
margin-top: 40px;
|
||
padding-top: 16px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.mock-shell {
|
||
display: grid;
|
||
grid-template-columns: 272px minmax(0, 1fr);
|
||
min-height: 680px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
background: #0a0a0a;
|
||
}
|
||
|
||
.mock-sidebar {
|
||
position: relative;
|
||
background: #171717;
|
||
border-right: 1px solid rgb(255 255 255 / 0.07);
|
||
color: #fafafa;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.mock-nav {
|
||
padding: 12px 10px 8px;
|
||
border-bottom: 1px solid rgb(255 255 255 / 0.07);
|
||
}
|
||
|
||
.mock-nav-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
height: 24px;
|
||
color: #a1a1a1;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.mock-sidebar-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
height: 32px;
|
||
padding: 8px 10px 4px 12px;
|
||
}
|
||
|
||
.mock-sidebar-title {
|
||
color: rgb(161 161 161 / 0.8);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mock-icon-buttons {
|
||
display: inline-flex;
|
||
gap: 6px;
|
||
}
|
||
|
||
.mock-icon-button {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 24px;
|
||
height: 24px;
|
||
border: 0;
|
||
border-radius: 6px;
|
||
background: transparent;
|
||
color: #a1a1a1;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.mock-badge-dot {
|
||
position: absolute;
|
||
top: 1px;
|
||
right: 1px;
|
||
min-width: 12px;
|
||
height: 12px;
|
||
border-radius: 999px;
|
||
background: #fafafa;
|
||
color: #0a0a0a;
|
||
font-size: 9px;
|
||
font-weight: 700;
|
||
line-height: 12px;
|
||
text-align: center;
|
||
}
|
||
|
||
.mock-scope-strip {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
margin: 4px 8px 8px;
|
||
padding: 7px 8px;
|
||
border: 1px solid rgb(255 255 255 / 0.07);
|
||
border-radius: 8px;
|
||
background: color-mix(in srgb, #fafafa 5%, #171717);
|
||
}
|
||
|
||
.mock-scope-title {
|
||
display: flex;
|
||
min-width: 0;
|
||
align-items: center;
|
||
gap: 7px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mock-scope-title span:last-child {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.mock-scope-meta {
|
||
color: #a1a1a1;
|
||
font-size: 11px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.mock-list {
|
||
padding: 0 6px 12px;
|
||
}
|
||
|
||
.mock-host-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
height: 28px;
|
||
margin-top: 6px;
|
||
padding: 0 6px;
|
||
color: #a1a1a1;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.05em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.mock-host-left {
|
||
display: flex;
|
||
min-width: 0;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.mock-host-left span:last-child {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.mock-status-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
border-radius: 999px;
|
||
color: #a1a1a1;
|
||
font-size: 10px;
|
||
font-weight: 600;
|
||
letter-spacing: 0;
|
||
text-transform: none;
|
||
}
|
||
|
||
.mock-status-light {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 999px;
|
||
background: var(--success);
|
||
}
|
||
|
||
.mock-status-light.warn {
|
||
background: var(--warning);
|
||
}
|
||
|
||
.mock-card {
|
||
margin: 3px 0;
|
||
padding: 7px 8px;
|
||
border: 1px solid transparent;
|
||
border-radius: 7px;
|
||
background: transparent;
|
||
}
|
||
|
||
.mock-card.active {
|
||
border-color: rgb(255 255 255 / 0.07);
|
||
background: color-mix(in srgb, #fafafa 10%, transparent);
|
||
box-shadow: 0 1px 2px color-mix(in srgb, #fafafa 3%, transparent);
|
||
}
|
||
|
||
.mock-card.child {
|
||
margin-left: 14px;
|
||
background: color-mix(in srgb, #404040 10%, #171717);
|
||
box-shadow: inset 0 0 0 1px color-mix(in srgb, #404040 16%, transparent);
|
||
}
|
||
|
||
.mock-card-top {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.mock-card-status {
|
||
width: 7px;
|
||
height: 7px;
|
||
flex: 0 0 auto;
|
||
border-radius: 999px;
|
||
background: var(--success);
|
||
}
|
||
|
||
.mock-card-status.yellow {
|
||
background: var(--warning);
|
||
}
|
||
|
||
.mock-card-status.gray {
|
||
background: #737373;
|
||
}
|
||
|
||
.mock-card-title {
|
||
overflow: hidden;
|
||
color: #fafafa;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.mock-card-subtitle {
|
||
overflow: hidden;
|
||
margin-top: 3px;
|
||
padding-left: 14px;
|
||
color: #a1a1a1;
|
||
font-size: 11px;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.mock-agent-row {
|
||
margin: 6px 0 0 14px;
|
||
padding: 5px 6px;
|
||
border-radius: 6px;
|
||
background: color-mix(in srgb, #404040 18%, transparent);
|
||
color: #d4d4d8;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.mock-workspace-surface {
|
||
position: relative;
|
||
background:
|
||
linear-gradient(90deg, rgb(255 255 255 / 0.025), transparent 40%),
|
||
#0a0a0a;
|
||
padding: 18px;
|
||
}
|
||
|
||
.mock-workspace-tabs {
|
||
display: flex;
|
||
gap: 6px;
|
||
border-bottom: 1px solid var(--border);
|
||
padding-bottom: 10px;
|
||
}
|
||
|
||
.mock-tab {
|
||
border: 1px solid var(--border);
|
||
border-radius: 7px;
|
||
background: var(--card);
|
||
color: var(--muted-foreground);
|
||
font-size: 12px;
|
||
padding: 5px 10px;
|
||
}
|
||
|
||
.mock-tab.active {
|
||
color: var(--foreground);
|
||
background: color-mix(in srgb, var(--foreground) 8%, var(--card));
|
||
}
|
||
|
||
.mock-terminal {
|
||
margin-top: 22px;
|
||
color: #d4d4d8;
|
||
font-family:
|
||
"SFMono-Regular",
|
||
Consolas,
|
||
"Liberation Mono",
|
||
monospace;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.mock-terminal-line {
|
||
margin: 7px 0;
|
||
}
|
||
|
||
.mock-popover {
|
||
position: absolute;
|
||
top: 58px;
|
||
left: 58px;
|
||
width: 288px;
|
||
overflow: hidden;
|
||
border: 1px solid rgb(255 255 255 / 0.13);
|
||
border-radius: 12px;
|
||
background: rgb(10 10 10 / 0.98);
|
||
box-shadow: 0 18px 40px rgb(0 0 0 / 0.45);
|
||
color: #fafafa;
|
||
z-index: 2;
|
||
}
|
||
|
||
.mock-menu-label {
|
||
padding: 12px 16px 7px;
|
||
color: #a1a1a1;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.mock-segmented {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
margin: 0 12px 10px;
|
||
overflow: hidden;
|
||
border: 1px solid rgb(255 255 255 / 0.16);
|
||
border-radius: 9px;
|
||
}
|
||
|
||
.mock-segmented span {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 28px;
|
||
border-left: 1px solid rgb(255 255 255 / 0.1);
|
||
color: #fafafa;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mock-segmented span:first-child {
|
||
border-left: 0;
|
||
}
|
||
|
||
.mock-segmented .selected {
|
||
background: rgb(255 255 255 / 0.11);
|
||
}
|
||
|
||
.mock-menu-separator {
|
||
height: 1px;
|
||
background: rgb(255 255 255 / 0.07);
|
||
margin: 7px 8px;
|
||
}
|
||
|
||
.mock-menu-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
min-height: 32px;
|
||
gap: 12px;
|
||
padding: 0 16px;
|
||
color: #fafafa;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.mock-menu-row .right {
|
||
color: #a1a1a1;
|
||
font-size: 12px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.mock-menu-row.host {
|
||
min-height: 38px;
|
||
}
|
||
|
||
.mock-menu-row.selected-host {
|
||
background: rgb(255 255 255 / 0.08);
|
||
}
|
||
|
||
.mock-host-menu-top {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
padding: 10px 12px 8px;
|
||
}
|
||
|
||
.mock-host-menu-top strong {
|
||
font-size: 12px;
|
||
}
|
||
|
||
.mock-host-menu-top span {
|
||
color: #a1a1a1;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.mock-host-select {
|
||
display: flex;
|
||
min-width: 0;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.mock-host-select strong {
|
||
overflow: hidden;
|
||
font-size: 13px;
|
||
line-height: 1.2;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.mock-host-select span {
|
||
color: #a1a1a1;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.mock-check {
|
||
color: var(--success);
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.mock-check.hidden {
|
||
opacity: 0;
|
||
}
|
||
|
||
.mock-host-filter-row {
|
||
display: grid;
|
||
grid-template-columns: 18px 1fr auto;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-height: 48px;
|
||
margin: 0 8px;
|
||
border-radius: 8px;
|
||
color: #fafafa;
|
||
font-size: 13px;
|
||
padding: 5px 8px;
|
||
}
|
||
|
||
.mock-host-filter-row.selected {
|
||
background: rgb(255 255 255 / 0.12);
|
||
}
|
||
|
||
.mock-host-filter-main {
|
||
min-width: 0;
|
||
}
|
||
|
||
.mock-host-filter-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
min-width: 0;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mock-host-filter-title span:last-child {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.mock-host-filter-detail {
|
||
margin-top: 1px;
|
||
overflow: hidden;
|
||
color: #a1a1a1;
|
||
font-size: 11px;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.mock-host-filter-meta {
|
||
color: #a1a1a1;
|
||
font-size: 10px;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.mock-host-filter-meta.blocked {
|
||
color: var(--warning);
|
||
}
|
||
|
||
.mock-toggle {
|
||
position: relative;
|
||
width: 24px;
|
||
height: 14px;
|
||
border-radius: 999px;
|
||
background: rgb(255 255 255 / 0.24);
|
||
}
|
||
|
||
.mock-toggle.on {
|
||
background: #fafafa;
|
||
}
|
||
|
||
.mock-toggle::after {
|
||
position: absolute;
|
||
top: 2px;
|
||
left: 2px;
|
||
width: 10px;
|
||
height: 10px;
|
||
border-radius: 999px;
|
||
background: #0a0a0a;
|
||
content: "";
|
||
}
|
||
|
||
.mock-toggle.on::after {
|
||
transform: translateX(10px);
|
||
}
|
||
|
||
.mock-search {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
height: 34px;
|
||
margin: 4px 12px 8px;
|
||
border: 1px solid rgb(255 255 255 / 0.14);
|
||
border-radius: 8px;
|
||
color: #a1a1a1;
|
||
font-size: 13px;
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.mock-project-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 9px;
|
||
height: 32px;
|
||
margin: 0 8px;
|
||
border-radius: 8px;
|
||
color: #fafafa;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
padding: 0 14px;
|
||
}
|
||
|
||
.mock-project-row.selected {
|
||
background: rgb(255 255 255 / 0.14);
|
||
}
|
||
|
||
.mock-swatch {
|
||
width: 7px;
|
||
height: 7px;
|
||
background: #ff4d57;
|
||
}
|
||
|
||
.mock-swatch.gray {
|
||
background: #737373;
|
||
}
|
||
|
||
.mock-swatch.green {
|
||
background: #22c55e;
|
||
}
|
||
|
||
.mock-swatch.blue {
|
||
background: #60a5fa;
|
||
}
|
||
|
||
@media (max-width: 780px) {
|
||
main {
|
||
padding: 32px 16px 48px;
|
||
}
|
||
|
||
h1 {
|
||
font-size: 28px;
|
||
}
|
||
|
||
.summary,
|
||
.phase,
|
||
.mock-shell {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.mock-workspace-surface {
|
||
min-height: 280px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<main>
|
||
<header>
|
||
<div class="eyebrow">Issue #4697 Product and Architecture Memo</div>
|
||
<h1>Making VMs, remote servers, and SSH workspaces first class in Orca</h1>
|
||
<p class="muted">
|
||
Users want one desktop Orca client to control local work plus multiple remote hosts
|
||
concurrently, without losing terminals or mentally switching into a different app.
|
||
</p>
|
||
<div class="summary">
|
||
<div class="panel">
|
||
<strong>Recommended model</strong>
|
||
One app window, many first-class execution hosts, grouped in the workspace sidebar.
|
||
</div>
|
||
<div class="panel">
|
||
<strong>Core architecture move</strong>
|
||
Replace global server switching with host-scoped routing and per-host session slices.
|
||
</div>
|
||
<div class="panel">
|
||
<strong>Settings principle</strong>
|
||
Client settings stay local; host settings are explicit and contextual.
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<section>
|
||
<h2>Executive Recommendation</h2>
|
||
<div class="callout decision">
|
||
Build a <strong>single-window, host-aware workbench</strong>. Local, SSH targets, VMs,
|
||
and paired Orca runtime servers should appear as execution hosts. Selecting a workspace
|
||
changes focus; it must not tear down other hosts' terminals.
|
||
</div>
|
||
<p>
|
||
The current destructive behavior comes from treating <code>activeRuntimeEnvironmentId</code>
|
||
as a global app mode. That model does not satisfy concurrent remote work. It should become
|
||
connection management and default selection, not the identity of the whole workbench.
|
||
</p>
|
||
<p>
|
||
Multiple app windows can be a later power-user feature, but should not be the first design.
|
||
A window-per-host approach fights the existing single-renderer architecture and weakens the
|
||
product goal of one client coordinating work across machines.
|
||
</p>
|
||
<p>
|
||
The product should feel configurable, not split into separate modes. A local-only user sees
|
||
today's Orca. A remote-heavy user can choose <code>All hosts</code> to operate like a
|
||
command center, or choose one host so the client feels almost entirely like that machine.
|
||
Those are two views over the same host registry and session model.
|
||
</p>
|
||
<p>
|
||
Do not design around a minimal MVP boundary here. The intended destination is the full
|
||
end-to-end model: concurrent local, SSH, VM-like, and paired-runtime hosts with safe
|
||
host-owned routing. Implementation can still be phased for review and risk control, but
|
||
the design should describe the complete product.
|
||
</p>
|
||
|
||
<h3>Vocabulary</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Term</th>
|
||
<th>Meaning</th>
|
||
<th>Examples</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Host</td>
|
||
<td>The machine or backend that owns execution, files, terminals, ports, and agents.</td>
|
||
<td>Local Mac, Windows VM over SSH, Linux builder, paired Orca runtime server.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>VM</td>
|
||
<td>
|
||
A loose product term for a remote machine. It may be backed by SSH today, a paired
|
||
runtime server, or future Orca-provided cloud infrastructure.
|
||
</td>
|
||
<td>Windows dev VM, Linux cloud builder, future Orca-hosted environment.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Visible host filter</td>
|
||
<td>Which hosts the sidebar shows. This is presentation/filtering, not teardown.</td>
|
||
<td>All hosts, Local Mac + Windows VM, Linux Builder only.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Default host</td>
|
||
<td>The host preselected for new workspace creation when a flow needs a target.</td>
|
||
<td>Local Mac remains the default while Windows VM is also visible.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Execution owner</td>
|
||
<td>The host identity attached to a repo/worktree/tab/PTY operation.</td>
|
||
<td><code>ssh:target-1</code>, <code>runtime:env-a</code>, <code>local</code>.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>What We Learned</h2>
|
||
<h3>Current behavior</h3>
|
||
<ul>
|
||
<li>Desktop Settings are backed by the local Electron main process store.</li>
|
||
<li>
|
||
<code>activeRuntimeEnvironmentId</code> is stored as a local client setting, but it acts
|
||
like a global workbench switch.
|
||
</li>
|
||
<li>
|
||
Switching runtime environments currently resets runtime-scoped renderer state and can
|
||
close remote terminals/browser pages.
|
||
</li>
|
||
<li>
|
||
SSH already has partial multi-host shape: repos carry <code>connectionId</code>, SSH
|
||
connection state is target-keyed, and remote workspace snapshots are keyed by target.
|
||
</li>
|
||
<li>
|
||
The renderer still has one live <code>WorkspaceSessionState</code>, so runtime servers
|
||
are not first-class concurrent peers yet.
|
||
</li>
|
||
</ul>
|
||
|
||
<h3>Core product requirement</h3>
|
||
<p>
|
||
A user must be able to start work on one host, switch attention to another host, then come
|
||
back without losing terminals, browser tabs, agent status, file context, or workspace
|
||
ordering. Host focus is a viewing and default-target choice. It is not permission to close
|
||
work on the old host.
|
||
</p>
|
||
|
||
<h3>Agent feedback</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Agent</th>
|
||
<th>Main recommendation</th>
|
||
<th>Useful nuance</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Cursor</td>
|
||
<td>Unify local, SSH, runtime, and VMs under an <code>ExecutionHostId</code>.</td>
|
||
<td>Partition session state by host and phase the refactor around routing keys.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Claude</td>
|
||
<td>Make runtime environments per-worktree, like SSH is already per-target.</td>
|
||
<td>
|
||
Keep local/runtime/SSH transport types distinct internally for now, but converge
|
||
them in the product model.
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Product Model</h2>
|
||
<p>
|
||
Orca should present every machine or remote backend as a <strong>host</strong>. A host can
|
||
be local, an SSH target, a paired Orca runtime server, or a VM profile backed by either SSH
|
||
or runtime RPC.
|
||
</p>
|
||
<p>
|
||
"VM" should remain a user-facing label, not a required separate transport in v1. Today a VM
|
||
can be an SSH target or a remote runtime server. Later, if Orca offers managed cloud
|
||
machines as a revenue source, those machines should slot into the same host model instead
|
||
of creating a parallel workspace concept.
|
||
</p>
|
||
<div>
|
||
<span class="tag">local</span>
|
||
<span class="tag">ssh:windows-vm</span>
|
||
<span class="tag">runtime:prod-builder</span>
|
||
<span class="tag">vm:win-only-product</span>
|
||
</div>
|
||
|
||
<h3>Sidebar behavior</h3>
|
||
<ul>
|
||
<li>Group workspaces by host in the sidebar.</li>
|
||
<li>Each host header shows label, health, reconnect state, and active agent count.</li>
|
||
<li>
|
||
Users choose visible hosts with a searchable multi-select menu modeled after the
|
||
existing Projects selector: sticky <code>All hosts</code>, search, checked rows, and
|
||
status metadata.
|
||
</li>
|
||
<li>
|
||
Selecting one visible host is the focused-host experience. Selecting multiple hosts is a
|
||
custom command-center view. Neither mode disconnects hidden hosts.
|
||
</li>
|
||
<li>New workspace defaults to the focused host, but existing workspaces keep their host.</li>
|
||
<li>Searching and Cmd+J should work across hosts, with host labels visible in results.</li>
|
||
<li>
|
||
Hide the host visibility UI entirely when there are no remote hosts. Local-only users
|
||
should keep today's sidebar.
|
||
</li>
|
||
</ul>
|
||
|
||
<h3>Host actions</h3>
|
||
<ul>
|
||
<li><code>Manage Host</code>: open a host details panel.</li>
|
||
<li><code>Reconnect</code> / <code>Disconnect</code>: connection lifecycle only.</li>
|
||
<li><code>Rename</code>: local display label.</li>
|
||
<li><code>Remove</code>: forget the host and clean up host-scoped local state safely.</li>
|
||
<li><code>Open in New Window</code>: optional later feature, not MVP.</li>
|
||
</ul>
|
||
|
||
<h3>Two Valid User Modes</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Mode</th>
|
||
<th>User intent</th>
|
||
<th>Experience</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>All hosts</td>
|
||
<td>"I want my local and remote work visible together."</td>
|
||
<td>
|
||
Sidebar shows host sections. Cmd+J/search can cross hosts. New work defaults to the
|
||
last focused host or an explicit host picker.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>One visible host</td>
|
||
<td>"Make this Orca client feel like the server."</td>
|
||
<td>
|
||
Sidebar shows only that host. Terminals/files/agents route to that host. Other hosts
|
||
keep running in the background and can be restored by changing scope.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Selected hosts</td>
|
||
<td>"Show my Mac plus the remote machines relevant to this task."</td>
|
||
<td>
|
||
Sidebar shows only checked host sections, with host health still visible. This is a
|
||
persistent presentation filter, not an ownership or teardown operation.
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h3>Primary user journeys</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Journey</th>
|
||
<th>Expected behavior</th>
|
||
<th>Implementation implication</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Add an SSH VM</td>
|
||
<td>
|
||
User saves a target, Orca shows it as a host, and new work can be created there.
|
||
</td>
|
||
<td>
|
||
Saved SSH targets become host registry entries with <code>ssh:<targetId></code>
|
||
execution identity.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Switch to Windows VM</td>
|
||
<td>
|
||
Sidebar narrows to Windows workspaces. Existing local terminals and agents keep
|
||
running.
|
||
</td>
|
||
<td>
|
||
Host visibility updates view/default state only. It must not call terminal/browser
|
||
close paths.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Create a workspace from All hosts</td>
|
||
<td>
|
||
Orca asks for or infers a target host, then binds the repo/worktree to that host
|
||
forever unless explicitly moved.
|
||
</td>
|
||
<td>
|
||
Creation flows use focused/default host as input. Later operations use owner host.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Open Settings while scoped to a remote host</td>
|
||
<td>
|
||
Settings still clearly says whether the user is editing client settings or that
|
||
host's details.
|
||
</td>
|
||
<td>
|
||
Host focus must never silently reinterpret local client settings as remote settings.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Reconnect after SSH auth expires</td>
|
||
<td>
|
||
Remote workspaces remain visible as unavailable/deferred until the user reauths.
|
||
</td>
|
||
<td>
|
||
Session hydration and sidebar rendering cannot require an active SSH tunnel.
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Sidebar Mock</h2>
|
||
<p>
|
||
Hosts should behave as a visibility filter above the existing organization controls. The
|
||
existing <code>Group by</code>, <code>Sort by</code>, filters, and project filter still
|
||
apply inside the visible host set.
|
||
</p>
|
||
<div class="callout">
|
||
There is no separate "Focused Host Mode" toggle. Showing only <code>Local Mac</code>,
|
||
<code>Windows VM</code>, or any other single host is the focused experience. Showing
|
||
<code>All hosts</code> or a checked subset is the command-center experience. New workspace
|
||
creation should still expose the target host explicitly when more than one host is visible.
|
||
</div>
|
||
<div class="mock-shell" aria-label="Mock Orca sidebar with host visibility menu">
|
||
<aside class="mock-sidebar">
|
||
<div class="mock-nav">
|
||
<div class="mock-nav-row">☰ Tasks</div>
|
||
<div class="mock-nav-row">◌ Automations</div>
|
||
<div class="mock-nav-row">⌕ Search</div>
|
||
</div>
|
||
<div class="mock-sidebar-header">
|
||
<div class="mock-sidebar-title">Workspaces</div>
|
||
<div class="mock-icon-buttons">
|
||
<div class="mock-icon-button">☷<span class="mock-badge-dot">2</span></div>
|
||
<div class="mock-icon-button">▦</div>
|
||
<div class="mock-icon-button">+</div>
|
||
</div>
|
||
</div>
|
||
<div class="mock-scope-strip">
|
||
<div class="mock-scope-title">
|
||
<span>◎</span>
|
||
<span>Hosts</span>
|
||
</div>
|
||
<div class="mock-scope-meta">3 of 4 · 1 offline ▸</div>
|
||
</div>
|
||
<div class="mock-list">
|
||
<div class="mock-host-header">
|
||
<div class="mock-host-left">
|
||
<span>▾</span>
|
||
<span>Local Mac</span>
|
||
</div>
|
||
<div class="mock-status-pill"><span class="mock-status-light"></span>2 agents</div>
|
||
</div>
|
||
<div class="mock-card active">
|
||
<div class="mock-card-top">
|
||
<span class="mock-card-status"></span>
|
||
<span class="mock-card-title">Control multiple Orca servers concurrently</span>
|
||
</div>
|
||
<div class="mock-card-subtitle">orca · Jinwoo-H/feature-control-multiple...</div>
|
||
<div class="mock-agent-row">Claude done · Cursor working · Codex working</div>
|
||
</div>
|
||
<div class="mock-card child">
|
||
<div class="mock-card-top">
|
||
<span class="mock-card-status yellow"></span>
|
||
<span class="mock-card-title">debug-claude-tui-terminal-read-blank</span>
|
||
</div>
|
||
<div class="mock-card-subtitle">child workspace · Codex working</div>
|
||
</div>
|
||
|
||
<div class="mock-host-header">
|
||
<div class="mock-host-left">
|
||
<span>▾</span>
|
||
<span>Windows VM</span>
|
||
</div>
|
||
<div class="mock-status-pill"><span class="mock-status-light"></span>Connected</div>
|
||
</div>
|
||
<div class="mock-card">
|
||
<div class="mock-card-top">
|
||
<span class="mock-card-status"></span>
|
||
<span class="mock-card-title">Win-only packaging fix</span>
|
||
</div>
|
||
<div class="mock-card-subtitle">desktop-app · PowerShell · 1 live port</div>
|
||
</div>
|
||
<div class="mock-card">
|
||
<div class="mock-card-top">
|
||
<span class="mock-card-status gray"></span>
|
||
<span class="mock-card-title">default branch</span>
|
||
</div>
|
||
<div class="mock-card-subtitle">desktop-app · main</div>
|
||
</div>
|
||
|
||
<div class="mock-host-header">
|
||
<div class="mock-host-left">
|
||
<span>▸</span>
|
||
<span>Linux Builder</span>
|
||
</div>
|
||
<div class="mock-status-pill"><span class="mock-status-light warn"></span>Reconnecting</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mock-popover">
|
||
<div class="mock-host-menu-top">
|
||
<strong>Hosts</strong>
|
||
<span>3 of 4 visible</span>
|
||
</div>
|
||
<div class="mock-search">⌕ Search hosts...</div>
|
||
<div class="mock-menu-row selected-host">
|
||
<span class="mock-check">✓</span>
|
||
<span>All hosts</span>
|
||
</div>
|
||
<div class="mock-host-filter-row selected">
|
||
<span class="mock-check">✓</span>
|
||
<div class="mock-host-filter-main">
|
||
<div class="mock-host-filter-title">
|
||
<span class="mock-status-light"></span>
|
||
<span>Local Mac</span>
|
||
</div>
|
||
<div class="mock-host-filter-detail">/Users/jinwoohong · default for new work</div>
|
||
</div>
|
||
<span class="mock-host-filter-meta">2 agents</span>
|
||
</div>
|
||
<div class="mock-host-filter-row selected">
|
||
<span class="mock-check">✓</span>
|
||
<div class="mock-host-filter-main">
|
||
<div class="mock-host-filter-title">
|
||
<span class="mock-status-light"></span>
|
||
<span>Windows VM</span>
|
||
</div>
|
||
<div class="mock-host-filter-detail">ssh://dev-win · 2 workspaces · PowerShell</div>
|
||
</div>
|
||
<span class="mock-host-filter-meta">Connected</span>
|
||
</div>
|
||
<div class="mock-host-filter-row selected">
|
||
<span class="mock-check">✓</span>
|
||
<div class="mock-host-filter-main">
|
||
<div class="mock-host-filter-title">
|
||
<span class="mock-status-light warn"></span>
|
||
<span>Linux Builder</span>
|
||
</div>
|
||
<div class="mock-host-filter-detail">runtime:prod-builder · cached workspaces visible</div>
|
||
</div>
|
||
<span class="mock-host-filter-meta blocked">Reconnecting</span>
|
||
</div>
|
||
<div class="mock-host-filter-row">
|
||
<span class="mock-check hidden">✓</span>
|
||
<div class="mock-host-filter-main">
|
||
<div class="mock-host-filter-title">
|
||
<span class="mock-status-light warn"></span>
|
||
<span>Old staging server</span>
|
||
</div>
|
||
<div class="mock-host-filter-detail">ssh://staging · update needed</div>
|
||
</div>
|
||
<span class="mock-host-filter-meta blocked">Blocked</span>
|
||
</div>
|
||
<div class="mock-menu-row">
|
||
<span>Manage hosts...</span>
|
||
<span class="right">›</span>
|
||
</div>
|
||
<div class="mock-menu-separator"></div>
|
||
|
||
<div class="mock-menu-label">Group by</div>
|
||
<div class="mock-segmented">
|
||
<span>None</span>
|
||
<span class="selected">Status</span>
|
||
<span>PR</span>
|
||
<span>Project</span>
|
||
</div>
|
||
|
||
<div class="mock-menu-separator"></div>
|
||
<div class="mock-menu-row">
|
||
<span>Sort by</span>
|
||
<span class="right">Manual ›</span>
|
||
</div>
|
||
<div class="mock-menu-row">
|
||
<span>Card layout</span>
|
||
<span class="right">Detailed ›</span>
|
||
</div>
|
||
<div class="mock-menu-row">
|
||
<span>Show properties</span>
|
||
<span class="right">6 ›</span>
|
||
</div>
|
||
|
||
<div class="mock-menu-separator"></div>
|
||
<div class="mock-menu-label">Filters</div>
|
||
<div class="mock-menu-row">
|
||
<span>☾ Hide sleeping</span>
|
||
<span class="mock-toggle on"></span>
|
||
</div>
|
||
<div class="mock-menu-row">
|
||
<span>⌘ Hide default branch</span>
|
||
<span class="mock-toggle"></span>
|
||
</div>
|
||
|
||
<div class="mock-menu-separator"></div>
|
||
<div class="mock-menu-label">Projects</div>
|
||
<div class="mock-search">⌕ Filter projects...</div>
|
||
<div class="mock-project-row selected"><span class="mock-swatch gray"></span>noqa</div>
|
||
<div class="mock-project-row"><span class="mock-swatch"></span>orca</div>
|
||
<div class="mock-project-row"><span class="mock-swatch green"></span>desktop-app</div>
|
||
<div class="mock-project-row"><span class="mock-swatch blue"></span>server-tools</div>
|
||
</div>
|
||
</aside>
|
||
<div class="mock-workspace-surface">
|
||
<div class="mock-workspace-tabs">
|
||
<span class="mock-tab active">feature-control-multi...</span>
|
||
<span class="mock-tab">Cursor opinion...</span>
|
||
<span class="mock-tab">Claude opinion...</span>
|
||
</div>
|
||
<div class="mock-terminal">
|
||
<div class="mock-terminal-line">
|
||
$ visible hosts: <span class="muted">Local Mac, Windows VM, Linux Builder</span>
|
||
</div>
|
||
<div class="mock-terminal-line">
|
||
Local Mac and remote workspaces stay alive while the sidebar changes focus.
|
||
</div>
|
||
<div class="mock-terminal-line">
|
||
Selecting <code>Windows VM</code> would hide local workspaces without closing them.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3>How this maps to the real sidebar</h3>
|
||
<ul>
|
||
<li>
|
||
Add <code>Hosts</code> above the current <code>Group by</code> control in
|
||
<code>SidebarWorkspaceOptionsMenu</code>, but only when at least one non-local host
|
||
exists. Use the same searchable multi-select popover pattern as the existing Projects
|
||
selector.
|
||
</li>
|
||
<li>
|
||
Keep <code>Group by</code> as <code>None / Status / PR / Project</code>; do not add
|
||
Host there because users still need status/project grouping inside a host.
|
||
</li>
|
||
<li>
|
||
Add a compact host summary strip below the <code>Workspaces</code> header when more
|
||
than one host exists. Hide it for local-only users.
|
||
</li>
|
||
<li>
|
||
In <code>All hosts</code>, render host headers above the existing grouped workspace
|
||
rows. When one or more specific hosts are selected, hide unchecked host sections without
|
||
disconnecting them.
|
||
</li>
|
||
<li>
|
||
Host header menus own connection actions: manage, reconnect, disconnect, rename, remove,
|
||
and later open in a separate window.
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Settings Model</h2>
|
||
<div class="callout">
|
||
The Settings page should be explicit: users are either editing the local client, a host,
|
||
or a repo/worktree. Do not let host focus silently change what a setting means.
|
||
</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Scope</th>
|
||
<th>Examples</th>
|
||
<th>Storage / routing</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Client settings</td>
|
||
<td>Theme, keybindings, language, window layout, telemetry, global UI preferences.</td>
|
||
<td>Existing local settings store.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Host settings</td>
|
||
<td>
|
||
Host label, connection details, auth/account selection, detected agents, shell
|
||
capability, server version, health, default clone location.
|
||
</td>
|
||
<td>Local host registry plus host RPC for server-owned facts.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Repo/worktree settings</td>
|
||
<td>Base ref, source-control AI recipe, tabs, layouts, per-worktree state.</td>
|
||
<td>Repo/worktree records tagged with host identity.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h3>Host Details surface</h3>
|
||
<p>
|
||
Do not build a second giant Settings app. Add a focused <strong>Host Details</strong>
|
||
panel reachable from each host header and from Settings > Servers.
|
||
</p>
|
||
<ul>
|
||
<li>Overview: status, latency/health, server version, protocol compatibility.</li>
|
||
<li>Connection: SSH/runtime endpoint, credentials, reconnect policy.</li>
|
||
<li>Agents: detected Codex/Claude/Cursor/Gemini/OpenCode on that host.</li>
|
||
<li>Terminal: default shell and host-specific terminal capabilities.</li>
|
||
<li>Storage: default workspace/clone directory, disk/port/resource status.</li>
|
||
<li>Danger zone: disconnect, remove, clear cached host session.</li>
|
||
</ul>
|
||
|
||
<h3>Effective settings rule</h3>
|
||
<p>
|
||
For ambiguous preferences, use inheritance:
|
||
<code>effective = host override ?? client default</code>. Show when a host is overriding
|
||
the client default, and provide a clear reset action.
|
||
</p>
|
||
<p>
|
||
In the settings navigation, keep client settings as the default top-level destination.
|
||
Add a <code>Servers / Hosts</code> area for saved local, SSH, runtime, and VM hosts. From
|
||
the sidebar, <code>Manage Host</code> should deep-link directly to the selected host
|
||
details, not change the meaning of the entire Settings page.
|
||
</p>
|
||
<p>
|
||
For settings that can vary by host, a scoped selector is enough: a dropdown such as
|
||
<code>Apply to: Client default / Local Mac / Windows VM / Linux Builder</code>. This is
|
||
similar to Windows settings surfaces that let users choose whether a setting applies to
|
||
Windows or WSL. The key rule is that the selected settings scope must be visible in the
|
||
control itself, not inferred from sidebar focus.
|
||
</p>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>How SSH Fits</h2>
|
||
<div class="callout decision">
|
||
SSH should be treated as a first-class host type. It is not a side feature. It is the
|
||
closest existing implementation to the desired multi-host model.
|
||
</div>
|
||
<p>
|
||
SSH already has many of the properties this design needs: target-keyed connection state,
|
||
repo-level <code>connectionId</code>, per-target remote workspace snapshots, SSH-aware PTY
|
||
IDs, remote file operations, and port forwarding. The new model should give those pieces a
|
||
clearer product wrapper rather than replacing them.
|
||
</p>
|
||
|
||
<h3>Host identity</h3>
|
||
<p>Conceptually, host IDs should look like this:</p>
|
||
<ul>
|
||
<li><code>local</code></li>
|
||
<li><code>ssh:<targetId></code></li>
|
||
<li><code>runtime:<environmentId></code></li>
|
||
</ul>
|
||
<p>
|
||
An SSH connection is both a saved connection profile and an execution host. The connection
|
||
profile stores how to reach the machine. The execution host identity is what repos,
|
||
worktrees, PTYs, agents, browser sessions, and file operations route through.
|
||
</p>
|
||
<p>
|
||
A VM is not a special case here. A Windows VM reached over SSH is
|
||
<code>ssh:<targetId></code>. A cloud VM running a paired Orca runtime is
|
||
<code>runtime:<environmentId></code>. A future Orca-managed VM should still expose a
|
||
host ID and adapter rather than leaking cloud-provider details into workspace routing.
|
||
</p>
|
||
|
||
<h3>SSH lifecycle</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>State</th>
|
||
<th>Sidebar representation</th>
|
||
<th>User action</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Saved, never connected</td>
|
||
<td>Host appears as available to connect, with no live workspaces yet.</td>
|
||
<td>Connect, edit details, or create workspace on host.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Connected</td>
|
||
<td>Host section shows health, active workspaces, live ports, and agent count.</td>
|
||
<td>Create/open work, launch terminals, manage host, disconnect.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Needs auth</td>
|
||
<td>Host section remains visible with a clear auth-needed status.</td>
|
||
<td>Enter passphrase/password or edit credentials.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Offline/reconnecting</td>
|
||
<td>Previously known workspaces remain visible but disabled where necessary.</td>
|
||
<td>Retry, inspect error, edit connection, or remove host.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Removed</td>
|
||
<td>Host disappears after confirmation; cached host sessions are cleaned safely.</td>
|
||
<td>Explicit destructive action only.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h3>SSH in the sidebar</h3>
|
||
<p>
|
||
A saved SSH target appears as a host section, not merely as a small badge on a project.
|
||
The transport can still be visible in secondary text:
|
||
</p>
|
||
<ul>
|
||
<li><code>Windows VM · SSH · Connected · 2 agents</code></li>
|
||
<li><code>Linux Builder · SSH · Reconnecting</code></li>
|
||
<li><code>Remote QA Box · SSH · Needs passphrase</code></li>
|
||
</ul>
|
||
|
||
<h3>Host Details for SSH</h3>
|
||
<ul>
|
||
<li>Connection label, hostname, username, port, and auth method.</li>
|
||
<li>Reconnect status, last error, passphrase/password prompts.</li>
|
||
<li>Remote default clone/workspace directory.</li>
|
||
<li>Detected shells, installed coding agents, and terminal capabilities.</li>
|
||
<li>Remote ports, relay capability, server/relay version, and health.</li>
|
||
<li>Danger actions: disconnect, remove host, clear cached sessions.</li>
|
||
</ul>
|
||
|
||
<h3>Keep adapters distinct</h3>
|
||
<p>
|
||
The UI and routing language should converge, but the implementation should not force SSH
|
||
and paired runtime servers into one transport too early. Use an adapter shape:
|
||
</p>
|
||
<ul>
|
||
<li><code>{ kind: 'local' }</code></li>
|
||
<li><code>{ kind: 'ssh', targetId }</code></li>
|
||
<li><code>{ kind: 'runtime', environmentId }</code></li>
|
||
</ul>
|
||
<p>
|
||
Features ask "which host owns this worktree?" and dispatch through the right adapter. That
|
||
preserves SSH-specific filesystem, PTY, relay, and port-forward behavior while making the
|
||
product feel coherent.
|
||
</p>
|
||
|
||
<h3>SSH-specific risks</h3>
|
||
<ul>
|
||
<li>Passphrase-protected targets may be deferred; do not hide their restored workspaces.</li>
|
||
<li>Windows SSH hosts use different path and shell semantics; never assume local POSIX paths.</li>
|
||
<li>Relay capabilities may differ by version; Host Details should surface unsupported features.</li>
|
||
<li>Remote PTY session IDs must remain connection-scoped so reconnects do not attach the wrong host.</li>
|
||
<li>SSH targets may not run the same Orca relay or agent versions as local; gate features by capability.</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Architecture Direction</h2>
|
||
<h3>Introduce execution host identity</h3>
|
||
<p>
|
||
Add a normalized host/routing key that can represent local, SSH, runtime, and future VM
|
||
hosts.
|
||
</p>
|
||
<ul>
|
||
<li><code>local</code></li>
|
||
<li><code>ssh:<targetId></code></li>
|
||
<li><code>runtime:<environmentId></code></li>
|
||
</ul>
|
||
|
||
<h3>Bind work to hosts</h3>
|
||
<ul>
|
||
<li>Repos carry host identity.</li>
|
||
<li>Worktrees derive host identity from repo.</li>
|
||
<li>Terminal/browser/editor operations route by worktree host, not by global app mode.</li>
|
||
<li>New workspace creation uses focused/default host only as an initial value.</li>
|
||
</ul>
|
||
|
||
<h3>Data ownership rules</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Data</th>
|
||
<th>Owner</th>
|
||
<th>Rule</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Theme, keybindings, window/sidebar layout</td>
|
||
<td>Client</td>
|
||
<td>Always local. Host focus must not change what these settings mean.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Host registry and labels</td>
|
||
<td>Client</td>
|
||
<td>Local list of known hosts, with runtime/SSH-specific connection fields.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Paired runtime server configuration</td>
|
||
<td>Client</td>
|
||
<td>
|
||
The desktop client owns the saved server entry, connection policy, and user-facing
|
||
settings for that server. The server reports execution facts and capabilities.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Repos and worktrees</td>
|
||
<td>Host</td>
|
||
<td>Every repo/worktree must carry or derive host identity.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Terminal/browser handles</td>
|
||
<td>Host</td>
|
||
<td>IDs must be host-scoped and routed through the owner host.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Agent detection and account availability</td>
|
||
<td>Host or client depending on runtime</td>
|
||
<td>Show source clearly. Installed tools on a VM are not the same as local tools.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h3>Source of truth rules</h3>
|
||
<ul>
|
||
<li>
|
||
The local client owns the list of known hosts, display labels, sidebar scope, and UI
|
||
preferences.
|
||
</li>
|
||
<li>
|
||
The owning host owns execution facts: filesystem, shell, git state, running terminals,
|
||
live browser sessions, ports, and installed agents.
|
||
</li>
|
||
<li>
|
||
Repo/worktree records must include enough host identity to route without consulting the
|
||
current sidebar scope.
|
||
</li>
|
||
<li>
|
||
Cached remote snapshots are client-side projections of host-owned state. They can render
|
||
offline UI, but they are not permission to mutate remote state while disconnected.
|
||
</li>
|
||
</ul>
|
||
|
||
<h3>Partition session state</h3>
|
||
<p>
|
||
Move from one global session blob toward host-partitioned session slices. The renderer can
|
||
still render one unified workbench, but persistence and routing should be host-aware.
|
||
</p>
|
||
<ul>
|
||
<li><code>workspaceSessionsByHost.local</code></li>
|
||
<li><code>workspaceSessionsByHost.ssh:windows-vm</code></li>
|
||
<li><code>workspaceSessionsByHost.runtime:prod-builder</code></li>
|
||
</ul>
|
||
|
||
<h3>Terminal and session identity direction</h3>
|
||
<p>
|
||
Existing SSH PTY IDs already point toward the right model:
|
||
<code>src/shared/ssh-pty-id.ts</code> embeds the SSH connection ID into app-visible PTY
|
||
IDs so relay-local IDs cannot collide after restore. Runtime and local sessions should
|
||
converge on the same principle.
|
||
</p>
|
||
<ul>
|
||
<li>
|
||
Prefer structured ownership at API boundaries: <code>{ hostId, sessionId }</code> for
|
||
new code and persisted records.
|
||
</li>
|
||
<li>
|
||
Keep string wrappers for compatibility where existing tab/layout maps require string
|
||
keys, but make them parseable and host-scoped.
|
||
</li>
|
||
<li>
|
||
Do not let cleanup paths infer runtime ownership from
|
||
<code>getActiveRuntimeTarget(get().settings)</code>. A terminal stop/kill must use the
|
||
owner host of the worktree, tab, or PTY being closed.
|
||
</li>
|
||
<li>
|
||
Audit maps such as <code>ptyIdsByTabId</code>, <code>terminalLayoutsByTabId</code>,
|
||
browser page maps, and tab order maps for assumptions that IDs are globally unique.
|
||
</li>
|
||
</ul>
|
||
|
||
<h3>Stop using global active runtime for mutations</h3>
|
||
<p>
|
||
<code>activeRuntimeEnvironmentId</code> should not decide where a terminal, file operation,
|
||
git action, browser page, or agent launch goes. The owning worktree should decide.
|
||
</p>
|
||
|
||
<h3>Compatibility bridge</h3>
|
||
<p>
|
||
During migration, keep a compatibility layer where <code>activeRuntimeEnvironmentId</code>
|
||
can still provide a default host for legacy flows. New code should ask for host identity
|
||
from the repo/worktree/tab owner first, then fall back to the focused/default host only for
|
||
creation flows.
|
||
</p>
|
||
<p>
|
||
Existing remote-server users should not experience a jarring migration. Their current
|
||
active runtime becomes the focused/default host. The main visible change is additive: they
|
||
may now also see local-machine workspaces when using <code>All hosts</code>, and can narrow
|
||
back to the remote server by selecting only that host.
|
||
</p>
|
||
|
||
<h3>Version skew and capability gating</h3>
|
||
<p>
|
||
Multi-host Orca must assume hosts can run different Orca builds. A new desktop client may
|
||
connect to an old runtime server, an old desktop client may connect to a newer server, and
|
||
two remote hosts may expose different feature sets at the same time.
|
||
</p>
|
||
<p>
|
||
The current runtime compatibility foundation is already the right shape:
|
||
<code>src/shared/protocol-version.ts</code> declares protocol versions and capabilities,
|
||
<code>src/shared/protocol-compat.ts</code> evaluates whether client/server protocol ranges
|
||
overlap, and <code>runtime.status.get</code> returns optional protocol and capability
|
||
fields. The host model should promote that from a hidden preflight into visible per-host
|
||
state.
|
||
</p>
|
||
<p>
|
||
The important rule is that <strong>Orca app versions are diagnostic labels, not the
|
||
compatibility contract</strong>. A desktop client at app version 1.8.0 should be allowed
|
||
to use a server at app version 1.7.2 if their runtime protocol ranges overlap. Exact app
|
||
version equality would make remote hosts unnecessarily brittle, especially for SSH boxes,
|
||
long-lived VMs, and future cloud-hosted machines that may update on a different cadence
|
||
from the user's laptop.
|
||
</p>
|
||
<p>
|
||
Compatibility should be modeled as host state, not app state. Each host registry entry
|
||
needs a verdict such as <code>compatible</code>, <code>server-too-old</code>,
|
||
<code>client-too-old</code>, <code>unknown</code>, or <code>degraded</code>, plus the
|
||
capabilities that were actually advertised by that host. The sidebar, Settings, worktree
|
||
actions, terminal launch, browser actions, hosted review polling, and background agents
|
||
should all ask the owning host for this verdict before mutating anything.
|
||
</p>
|
||
<h4>Handshake model</h4>
|
||
<ol>
|
||
<li>
|
||
When a host connects, call its cheapest status endpoint first, such as
|
||
<code>runtime.status.get</code> for runtime servers or the SSH relay status equivalent
|
||
for SSH hosts.
|
||
</li>
|
||
<li>
|
||
Record <code>appVersion</code>, <code>runtimeProtocolVersion</code>,
|
||
<code>minCompatibleRuntimeClientVersion</code>, <code>capabilities</code>,
|
||
platform, relay version, health, and last successful check time on the host registry
|
||
entry.
|
||
</li>
|
||
<li>
|
||
Evaluate protocol compatibility per host. A host is hard-blocked only when the client is
|
||
below the server's minimum client protocol, or the server is below the client's minimum
|
||
server protocol.
|
||
</li>
|
||
<li>
|
||
Evaluate optional features from <code>capabilities</code>. Missing capabilities disable
|
||
only the specific feature, not the whole host.
|
||
</li>
|
||
<li>
|
||
Cache the verdict per host and re-check on reconnect, explicit refresh, server update,
|
||
relay reinstall, or status-version change.
|
||
</li>
|
||
</ol>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Scenario</th>
|
||
<th>Expected behavior</th>
|
||
<th>UI treatment</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>New client, old compatible server</td>
|
||
<td>
|
||
Connect normally. Older servers may omit newer fields; clients must treat missing
|
||
optional fields as unavailable, not corrupt. Enable only features advertised by the
|
||
server's <code>capabilities</code>.
|
||
</td>
|
||
<td>
|
||
Host is healthy. Newer unsupported features are hidden or disabled with a short
|
||
"requires newer server" explanation.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>New client, too-old server</td>
|
||
<td>
|
||
Block runtime RPCs after the status handshake reports <code>server-too-old</code>.
|
||
Keep cached host sidebar/session state visible where possible, but do not launch
|
||
terminals, agents, git mutations, browser actions, or file writes against that host.
|
||
</td>
|
||
<td>
|
||
Host header shows <code>Update server</code>. Host Details shows server protocol,
|
||
required protocol, and update guidance.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Old client, newer server</td>
|
||
<td>
|
||
Server should allow the client if the client's protocol is within the server's
|
||
supported range. Otherwise the server reports <code>client-too-old</code> before
|
||
accepting mutating calls.
|
||
</td>
|
||
<td>
|
||
Host header shows <code>Update client</code> when blocked. Do not attempt partial
|
||
writes after a hard incompatibility verdict.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Mixed compatible hosts</td>
|
||
<td>
|
||
Each host gets its own status and capability set. One old host must not downgrade
|
||
the UI for another newer host.
|
||
</td>
|
||
<td>
|
||
Disable unsupported actions only inside the owning host/workspace. Keep other hosts
|
||
fully functional.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>SSH relay or agent-hook skew</td>
|
||
<td>
|
||
Treat relay/agent-hook versions as host capabilities. Terminal/file basics can work
|
||
while newer agent attribution, port detection, orchestration, or browser features
|
||
are unavailable.
|
||
</td>
|
||
<td>
|
||
Host Details lists relay version, detected agents, missing capabilities, and repair
|
||
actions.
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<ul>
|
||
<li>Never require exact app-version equality between client and server.</li>
|
||
<li>Use protocol range overlap for hard allow/block decisions.</li>
|
||
<li>Use capabilities for additive feature availability.</li>
|
||
<li>
|
||
Treat missing protocol fields as protocol 0 so pre-contract servers produce a clear
|
||
update-server path instead of mysterious runtime failures.
|
||
</li>
|
||
<li>
|
||
Prefer read-only cached visibility for incompatible hosts. Users should still understand
|
||
that the host exists and why it is unavailable.
|
||
</li>
|
||
<li>
|
||
Cache compatibility per host, not globally, and invalidate it on reconnect, server
|
||
update, or status version change.
|
||
</li>
|
||
<li>
|
||
Host Details should expose app version, protocol version, minimum compatible peer
|
||
version, and capabilities for debugging.
|
||
</li>
|
||
</ul>
|
||
|
||
<h4>Operational contract for mismatched versions</h4>
|
||
<p>
|
||
The client should treat every remote host as an independent compatibility island. A new
|
||
client connected to an old server, an old client connected to a new server, and a fully
|
||
current local host can all coexist in the same sidebar. Compatibility must be checked at
|
||
the host boundary before the host is allowed to receive mutating commands.
|
||
</p>
|
||
<ol>
|
||
<li>
|
||
<strong>Status-only bootstrap:</strong> the first call to a remote host must be a
|
||
backwards-compatible status handshake. It should not depend on the full runtime API
|
||
being compatible. If even the status endpoint is unavailable, classify the host as
|
||
<code>unknown</code> or <code>needs update</code> and keep cached read-only state visible.
|
||
</li>
|
||
<li>
|
||
<strong>Hard compatibility verdict:</strong> compare the client's protocol range with
|
||
the host's reported protocol range. If they do not overlap, the host remains visible but
|
||
disabled for terminals, agents, git writes, file writes, browser actions, and settings
|
||
mutations.
|
||
</li>
|
||
<li>
|
||
<strong>Soft capability verdict:</strong> if the protocol range is compatible, use the
|
||
host's advertised capabilities to enable or disable individual features. This is how a
|
||
new desktop client can safely work with an older but still compatible server.
|
||
</li>
|
||
<li>
|
||
<strong>Per-command guard:</strong> every host-owned mutation should check the host
|
||
compatibility/capability verdict at dispatch time, not only when rendering the button.
|
||
UI hiding prevents confusion; dispatch guards prevent stale UI or background workflows
|
||
from writing to an incompatible host.
|
||
</li>
|
||
<li>
|
||
<strong>Update direction:</strong> when blocked, the host registry should preserve a
|
||
reason code such as <code>server-too-old</code>, <code>client-too-old</code>, or
|
||
<code>missing-capability</code>. The sidebar badge and Host Details page can then say
|
||
exactly whether the user should update the desktop client, update/reinstall the remote
|
||
server or relay, or simply accept that one optional feature is unavailable on that host.
|
||
</li>
|
||
<li>
|
||
<strong>Owner-scoped fallback:</strong> if an older server cannot answer a newer API,
|
||
the client may fall back only for the host that owns the request, and only to a known
|
||
compatible older endpoint. It must not silently reroute the request to the focused host
|
||
or to the local machine.
|
||
</li>
|
||
</ol>
|
||
<div class="callout warning">
|
||
A mismatched remote host should never force the whole Orca client into a degraded mode.
|
||
In <code>All hosts</code>, incompatible hosts are visible with blocked actions while
|
||
compatible hosts continue working normally. When the visible host filter contains only an
|
||
incompatible host, the main view should show the blocked state for that host and offer
|
||
repair/update actions instead of silently falling back to local execution.
|
||
</div>
|
||
|
||
<h3>Implementation inventory to start from</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Area</th>
|
||
<th>What to inspect</th>
|
||
<th>Desired direction</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Runtime switching</td>
|
||
<td><code>activeRuntimeEnvironmentId</code>, <code>getActiveRuntimeTarget</code></td>
|
||
<td>Use only as focused/default host compatibility, not mutation routing.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>SSH ownership</td>
|
||
<td><code>connectionId</code>, SSH target maps, remote workspace projections</td>
|
||
<td>Promote existing target-keyed concepts into host registry entries.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>PTY and browser sessions</td>
|
||
<td>Terminal IDs, browser page IDs, relay/session IDs</td>
|
||
<td>Namespace or validate by execution host before attach, close, or mutate.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Sidebar state</td>
|
||
<td>Workspace filters, grouping, project filters, selected workspace</td>
|
||
<td>
|
||
Add the Hosts visibility filter above existing organization controls; keep other
|
||
filters inside the visible host set.
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Settings</td>
|
||
<td>Local settings store, runtime settings, SSH target editing</td>
|
||
<td>Separate client settings from Host Details and inherited host overrides.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Phased Plan</h2>
|
||
<div class="phase">
|
||
<div class="phase-label">Phase 0</div>
|
||
<div>
|
||
<h3>Design the host identity layer</h3>
|
||
<p>
|
||
Define <code>ExecutionHostId</code>, host helpers, migration notes, and a grep map of
|
||
call sites that currently read <code>activeRuntimeEnvironmentId</code>.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="phase">
|
||
<div class="phase-label">Phase 1</div>
|
||
<div>
|
||
<h3>Separate connection management from workbench focus</h3>
|
||
<p>
|
||
Keep saved servers in Settings, but stop presenting one exclusive active server as
|
||
the whole app mode. Add focused/default host state for new work only.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="phase">
|
||
<div class="phase-label">Phase 2</div>
|
||
<div>
|
||
<h3>Keep terminals alive across local/server focus changes</h3>
|
||
<p>
|
||
Preserve runtime terminal/browser handles when changing focus. Do not close remote
|
||
resources unless the user disconnects or removes a host.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="phase">
|
||
<div class="phase-label">Phase 3</div>
|
||
<div>
|
||
<h3>Support concurrent local plus runtime and SSH hosts</h3>
|
||
<p>
|
||
Route repo/worktree/terminal operations by owner host. The full destination is grouped
|
||
local, SSH, and runtime hosts in one sidebar, with the visible host set as a filter.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="phase">
|
||
<div class="phase-label">Phase 4</div>
|
||
<div>
|
||
<h3>Generalize to many runtime servers</h3>
|
||
<p>
|
||
Use host-keyed connection maps, per-host health, and per-host session persistence.
|
||
Mirror the per-target discipline SSH already has.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="phase">
|
||
<div class="phase-label">Phase 5</div>
|
||
<div>
|
||
<h3>Unify local, SSH, runtime, and VM UI</h3>
|
||
<p>
|
||
Show host-grouped workspaces in the sidebar. Make SSH targets and paired runtime
|
||
servers feel like peers while preserving transport-specific implementation.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="phase">
|
||
<div class="phase-label">Phase 6</div>
|
||
<div>
|
||
<h3>Add optional detached host windows</h3>
|
||
<p>
|
||
Only after the host registry and session partitioning are solid, allow a host to be
|
||
opened in another window for power users.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Risks</h2>
|
||
<div class="callout warning">
|
||
The main engineering risk is not UI. It is preventing work from being routed to the wrong
|
||
machine once multiple hosts are alive at the same time.
|
||
</div>
|
||
<ul>
|
||
<li>
|
||
<strong>ID collisions:</strong> repo, worktree, tab, browser, and PTY IDs from many
|
||
hosts may coexist. IDs must be namespaced or validated by host.
|
||
</li>
|
||
<li>
|
||
<strong>Routing mistakes:</strong> file, git, terminal, browser, and agent operations
|
||
must route from worktree ownership, not a global active setting.
|
||
</li>
|
||
<li>
|
||
<strong>Session corruption:</strong> mixed-host state in one session blob can overwrite
|
||
or mis-hydrate remote work.
|
||
</li>
|
||
<li>
|
||
<strong>Protocol skew:</strong> different hosts may run different Orca versions and
|
||
capabilities. Compatibility and feature availability must be evaluated per host.
|
||
</li>
|
||
<li>
|
||
<strong>Security boundaries:</strong> auth/account settings and command execution must
|
||
not leak between hosts.
|
||
</li>
|
||
<li>
|
||
<strong>Resource use:</strong> many live hosts means many terminals, browser pages, and
|
||
subscriptions. Do not optimize this first, but leave room for later throttling, sleep,
|
||
and resource-budget policies.
|
||
</li>
|
||
<li>
|
||
<strong>UX ambiguity:</strong> users must always know which host owns a workspace and
|
||
which settings scope they are editing.
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Immediate Follow-ups</h2>
|
||
<ol>
|
||
<li>
|
||
Write a design doc for <code>ExecutionHostId</code>, host registry, session
|
||
partitioning, and migration.
|
||
</li>
|
||
<li>
|
||
Audit every <code>activeRuntimeEnvironmentId</code> read and classify it as default
|
||
host, owner host, or client setting.
|
||
</li>
|
||
<li>
|
||
Prototype a host-grouped sidebar with local plus one remote server.
|
||
</li>
|
||
<li>
|
||
Add a Host Details panel for saved servers without duplicating the full Settings page.
|
||
</li>
|
||
<li>
|
||
Preserve remote terminals when changing focus between local and remote contexts.
|
||
</li>
|
||
<li>
|
||
Fix the terminal-read bug where Claude Code's visible full-screen TUI output can appear
|
||
as blank lines through <code>orca terminal read</code>.
|
||
</li>
|
||
<li>
|
||
Add operator safeguards: do not close live agent terminals solely because transcript
|
||
output is quiet or blank.
|
||
</li>
|
||
</ol>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Acceptance Criteria</h2>
|
||
<ul>
|
||
<li>
|
||
A local-only user with no saved remote hosts sees no host visibility strip and no extra
|
||
sidebar complexity.
|
||
</li>
|
||
<li>
|
||
A user can connect Local Mac plus at least one SSH or runtime host and see both in one
|
||
sidebar under <code>All hosts</code>.
|
||
</li>
|
||
<li>
|
||
Selecting one host, multiple hosts, or sticky <code>All hosts</code> changes visible
|
||
workspaces and creation defaults without closing terminals, browser pages, or agents on
|
||
other hosts.
|
||
</li>
|
||
<li>
|
||
Settings always makes the edited scope visible: client, host, or repo/worktree.
|
||
</li>
|
||
<li>
|
||
SSH targets with expired auth or offline status still show restored workspaces and a
|
||
clear reconnect path.
|
||
</li>
|
||
<li>
|
||
A host with an incompatible server version is visible but blocked from unsafe mutations,
|
||
with clear update guidance in the host header and Host Details.
|
||
</li>
|
||
<li>
|
||
Optional features are enabled per host based on capabilities; one older host does not
|
||
disable newer features on another host.
|
||
</li>
|
||
<li>
|
||
File, git, terminal, browser, and agent actions route by the workspace owner host, not
|
||
by the current visible host filter.
|
||
</li>
|
||
<li>
|
||
Removing or disconnecting a host is the only path that intentionally tears down that
|
||
host's live remote resources.
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Agent Implementation Notes</h2>
|
||
<p>
|
||
A future agent picking this up should treat this as a multi-step architecture change, not a
|
||
sidebar-only feature. The UI can ship incrementally, but the safety boundary is routing.
|
||
</p>
|
||
<ul>
|
||
<li>
|
||
Start with a call-site inventory for <code>activeRuntimeEnvironmentId</code>,
|
||
<code>getActiveRuntimeTarget</code>, <code>connectionId</code>, and runtime/SSH PTY ID
|
||
helpers.
|
||
</li>
|
||
<li>
|
||
Classify each call site as: client setting, creation default, owner-host mutation,
|
||
status display, cache key, or compatibility fallback.
|
||
</li>
|
||
<li>
|
||
Add tests before broad rewrites. Regressions here can destroy terminals or route writes
|
||
to the wrong machine.
|
||
</li>
|
||
<li>
|
||
Preserve SSH behavior while making it more visible. Do not regress passphrase-deferred
|
||
reconnects, Windows remote paths, or remote port forwarding.
|
||
</li>
|
||
<li>
|
||
Avoid destructive switching in prototypes. A host focus change should never call close
|
||
on old host terminals unless explicitly testing disconnect/remove behavior.
|
||
</li>
|
||
<li>
|
||
If UI changes are made, verify local-only users still see the current simple sidebar
|
||
with no host-scope chrome.
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Resolved Decisions and Remaining Unknowns</h2>
|
||
<h3>Resolved decisions</h3>
|
||
<ul>
|
||
<li>VM is a loose host label, usually backed by SSH or paired runtime.</li>
|
||
<li>The design target is full end-to-end implementation, not an intentionally narrow MVP.</li>
|
||
<li>
|
||
In <code>All hosts</code>, new workspace creation should use the focused/default host
|
||
but make the target host visible and changeable.
|
||
</li>
|
||
<li>
|
||
Host-varying settings can use a visible host dropdown rather than a duplicate Settings app.
|
||
</li>
|
||
<li>The desktop client owns paired runtime server configuration.</li>
|
||
<li>
|
||
Existing active-runtime users migrate to a focused/default host and can still narrow the
|
||
app back to that server.
|
||
</li>
|
||
<li>
|
||
Background host resource policy can wait, as long as routing and teardown semantics leave
|
||
room for future throttling.
|
||
</li>
|
||
</ul>
|
||
<h3>Remaining unknowns</h3>
|
||
<ul>
|
||
<li>
|
||
Exact read-only behavior for offline hosts: recommended default is show cached sidebar,
|
||
tabs, terminal history, and metadata; disable filesystem/git/terminal mutations until
|
||
reconnect; allow local-only metadata edits such as rename/remove.
|
||
</li>
|
||
<li>
|
||
Exact shape of the host-scoped session ID migration: structured
|
||
<code>{ hostId, sessionId }</code> should be the destination, with parseable string
|
||
wrappers where legacy maps require string keys.
|
||
</li>
|
||
<li>Which status signals define "quiet but alive" for agent terminals?</li>
|
||
<li>
|
||
How future Orca-managed cloud hosts are provisioned, billed, and surfaced. They should
|
||
still conform to the same host registry and execution-owner model.
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<div class="footer">
|
||
Created from the issue #4697 investigation and agent reviews. Recommended next artifact:
|
||
a design doc with concrete type definitions, persistence migration, and routing call-site
|
||
inventory.
|
||
</div>
|
||
</main>
|
||
</body>
|
||
</html>
|