feat(remote): add desktop SSH remote instances lifecycle and UX (#515)

* feat(remote): add desktop SSH remote instances lifecycle and settings UX

* fix(remote): cancel SSH modal connect and correct desktop runtime detection

* fix(remote): unblock ssh auth flow and disconnect on instance removal

* fix(remote-ssh): harden remote lifecycle, auth probing, and port forwarding reliability

Improve SSH remote stability by correctly handling authenticated external probes, accepting ControlMaster handoff behavior, making reconnect detection tunnel-aware, and applying extra forwards through the shared master connection with local listener validation.

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
shekohex
2026-02-26 20:26:12 +02:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent 5250a20578
commit 27321d454b
12 changed files with 6335 additions and 128 deletions
+22
View File
@@ -36,6 +36,18 @@ const MODELS_METADATA_CACHE_TTL = 5 * 60 * 1000;
const CLIENT_RELOAD_DELAY_MS = 800;
const OPEN_CODE_READY_GRACE_MS = 12000;
const LONG_REQUEST_TIMEOUT_MS = 4 * 60 * 1000;
const OPENCHAMBER_VERSION = (() => {
try {
const packagePath = path.resolve(__dirname, '..', 'package.json');
const raw = fs.readFileSync(packagePath, 'utf8');
const pkg = JSON.parse(raw);
if (pkg && typeof pkg.version === 'string' && pkg.version.trim().length > 0) {
return pkg.version.trim();
}
} catch {
}
return 'unknown';
})();
const fsPromises = fs.promises;
const DEFAULT_FILE_SEARCH_LIMIT = 60;
const MAX_FILE_SEARCH_LIMIT = 400;
@@ -5791,6 +5803,7 @@ async function main(options = {}) {
}
const app = express();
const serverStartedAt = new Date().toISOString();
app.set('trust proxy', true);
expressApp = app;
server = http.createServer(app);
@@ -5822,6 +5835,15 @@ async function main(options = {}) {
});
});
app.get('/api/system/info', (req, res) => {
res.json({
openchamberVersion: OPENCHAMBER_VERSION,
runtime: process.env.OPENCHAMBER_RUNTIME || 'web',
pid: process.pid,
startedAt: serverStartedAt,
});
});
app.use((req, res, next) => {
if (
req.path.startsWith('/api/config/agents') ||