mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 16:59:43 +02:00
fix(tiles): Attach reads the response envelope; an exited agent gets no Attach
Found live: the attach and shell routes report a refusal in the envelope of
a 200 ({success: false}), and Attach read only res.ok, so a refused attach
remounted the tile as if it had worked. It now reads the envelope.
The refusal in question: an agent that exited in a live pane (paneExit, e.g.
a shell ended with `exit 3`) still has the pane's tmux client running, so
both routes refuse to start anything ("Session already has a running
process"), and the single view has no restart for it either. Its tile now
shows the exit with a pointer to Close session instead of an Attach button
that cannot work. A session with no PTY attached, or one whose socket closed
because it exited (4009), still gets Attach.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19645,10 +19645,17 @@ body.tile-grid-resizing--row * {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.tile-attach[hidden] {
|
||||
.tile-attach[hidden],
|
||||
.tile-attach [hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tile-attach-hint {
|
||||
max-width: 80%;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tile-attach-btn {
|
||||
padding: 4px 14px;
|
||||
font: inherit;
|
||||
|
||||
+30
-13
@@ -916,21 +916,27 @@ Object.assign(CodemanApp.prototype, {
|
||||
},
|
||||
|
||||
/**
|
||||
* What the tile's body should say instead of a terminal, or '' for none: a
|
||||
* session with no PTY attached (pid null), an agent that exited in a live
|
||||
* pane (paneExit), or a socket the server closed because the session exited
|
||||
* (4009). Attach was just pressed: nothing, while the server catches up.
|
||||
* What the tile's body should say instead of a terminal, or null for none: a
|
||||
* session with no PTY attached (pid null) or a socket the server closed
|
||||
* because the session exited (4009), both of which Attach can start again;
|
||||
* or an agent that exited in a live pane (paneExit), which it cannot: the
|
||||
* attach and shell routes refuse while the pane's tmux client still runs
|
||||
* ("already has a running process"), and the single view has no restart for
|
||||
* it either, so the tile says so and points at Close session instead. Attach
|
||||
* was just pressed: nothing, while the server catches up.
|
||||
*
|
||||
* @returns {{text: string, attachable: boolean}|null}
|
||||
*/
|
||||
_tileAttachReason(sessionId, tile) {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return '';
|
||||
if (!session) return null;
|
||||
const pending = this._tileAttachPending?.get(sessionId);
|
||||
if (pending && Date.now() - pending < 15000) return '';
|
||||
if (pending && Date.now() - pending < 15000) return null;
|
||||
const exited = typeof paneExitLabel === 'function' ? paneExitLabel(session.paneExit) : '';
|
||||
if (exited) return `The agent ${exited}`;
|
||||
if (session.pid === null) return 'Not attached';
|
||||
if (tile?._stoppedCode === 4009) return 'The session ended';
|
||||
return '';
|
||||
if (exited) return { text: `The agent ${exited}`, attachable: false };
|
||||
if (session.pid === null) return { text: 'Not attached', attachable: true };
|
||||
if (tile?._stoppedCode === 4009) return { text: 'The session ended', attachable: true };
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -961,15 +967,22 @@ Object.assign(CodemanApp.prototype, {
|
||||
e.stopPropagation();
|
||||
void this.attachTileSession(sessionId);
|
||||
});
|
||||
overlay.append(text, btn);
|
||||
const hint = document.createElement('span');
|
||||
hint.className = 'tile-attach-hint';
|
||||
hint.textContent = 'It cannot be restarted in place: close it from \u22EF (Close session).';
|
||||
overlay.append(text, btn, hint);
|
||||
entry.body.appendChild(overlay);
|
||||
entry.overlay = overlay;
|
||||
entry.overlayText = text;
|
||||
entry.overlayBtn = btn;
|
||||
entry.overlayHint = hint;
|
||||
}
|
||||
entry.overlay.hidden = false;
|
||||
const text = busy ? 'Attaching\u2026' : reason;
|
||||
const text = busy ? 'Attaching\u2026' : reason.text;
|
||||
if (entry.overlayText.textContent !== text) entry.overlayText.textContent = text;
|
||||
const attachable = busy || reason.attachable;
|
||||
entry.overlayBtn.hidden = !attachable;
|
||||
entry.overlayHint.hidden = attachable;
|
||||
entry.overlayBtn.disabled = busy;
|
||||
},
|
||||
|
||||
@@ -984,6 +997,8 @@ Object.assign(CodemanApp.prototype, {
|
||||
async attachTileSession(sessionId) {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return false;
|
||||
// An agent that exited in a live pane cannot be started again in place.
|
||||
if (this._tileAttachReason(sessionId, this._tileFor(sessionId))?.attachable === false) return false;
|
||||
this._tileAttachInFlight ||= new Set();
|
||||
if (this._tileAttachInFlight.has(sessionId)) return false;
|
||||
let url = `/api/sessions/${sessionId}/${session.mode === 'shell' ? 'shell' : 'interactive'}`;
|
||||
@@ -1003,7 +1018,9 @@ Object.assign(CodemanApp.prototype, {
|
||||
let ok = false;
|
||||
try {
|
||||
const res = await fetch(url, init);
|
||||
ok = !!res?.ok;
|
||||
// The routes report a refusal in the envelope of a 200.
|
||||
const body = await res?.json?.().catch(() => null);
|
||||
ok = !!res?.ok && body?.success !== false;
|
||||
} catch {
|
||||
ok = false;
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user