Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions scripts/test/focus-failure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,34 @@ assert.equal(
FAILURE_REMEDY.START_TERMINAL,
);

// The Fleet Runner desktop app words the SAME condition differently:
// `tab not found: <name>` (desktop/src/main/poller.ts). Unrecognised, it fell
// through to RETRY and drew a button that could never succeed — reported from
// /control through the feedback widget as "what is this? can you fix?".
//
// Verbatim from that report, including the trailing UI text the element picker
// captured, because that is what actually reaches this function.
assert.equal(
remedyForFailure("focus_tab → fleetcrown failed: tab not found: fleetcrown"),
FAILURE_REMEDY.START_SESSION,
);
assert.equal(
remedyForFailure(
"focus_tab → fleetcrown failed: tab not found: fleetcrown4m agoOpen on ControlRetry",
),
FAILURE_REMEDY.START_SESSION,
);
assert.equal(remedyForFailure("tab not found: orangecat"), FAILURE_REMEDY.START_SESSION);
assert.equal(remedyForFailure("TAB NOT FOUND: evig"), FAILURE_REMEDY.START_SESSION);

// Same precedence rule as the cloud wording: no terminal at all outranks it.
assert.equal(
remedyForFailure(`tab not found: x / ${FOCUS_FAILURE_PHRASE.NO_TERMINAL}`),
FAILURE_REMEDY.START_TERMINAL,
);

// Must not over-match: a message that merely mentions a tab is not this failure.
assert.equal(remedyForFailure("tab closed by user"), FAILURE_REMEDY.RETRY);
assert.equal(remedyForFailure("could not find the window"), FAILURE_REMEDY.RETRY);

console.log("✓ focus-failure tests passed");
19 changes: 19 additions & 0 deletions src/lib/terminals/focus-failure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ export const FOCUS_FAILURE_PHRASE = {
NO_TERMINAL: "no zellij session is running on the connected computer",
/** Zellij is up, but this project has no tab and no running agent. */
NO_SUCH_TARGET: "no zellij tab with that name in any active session",
/**
* The SAME condition, worded by the Fleet Runner desktop app instead of the
* cloud: `tab not found: <name>` (desktop/src/main/poller.ts).
*
* This is the drift the note above predicts, across a boundary it did not
* anticipate. The desktop runner is a separately shipped codebase that
* composes its own message, so a project focused through a local Fleet
* Runner produced text this classifier did not recognise, fell through to
* RETRY, and drew a Retry button that could never succeed — reported from
* /control through the feedback widget as "what is this? can you fix?".
*
* Matched here rather than fixed only in the desktop app, because Fleet
* Runner is versioned and released independently: every operator still on an
* older build would keep sending the old wording. A classifier that only
* understands the newest client is a classifier that lies about old ones.
*/
NO_SUCH_TARGET_DESKTOP: "tab not found:",
/** The tab exists and was found; focus just didn't take in time. */
FOCUS_TIMED_OUT: "focus did not take within",
} as const;
Expand All @@ -51,5 +68,7 @@ export function remedyForFailure(error: string | null | undefined): FailureRemed
const text = (error ?? "").toLowerCase();
if (text.includes(FOCUS_FAILURE_PHRASE.NO_TERMINAL)) return FAILURE_REMEDY.START_TERMINAL;
if (text.includes(FOCUS_FAILURE_PHRASE.NO_SUCH_TARGET)) return FAILURE_REMEDY.START_SESSION;
if (text.includes(FOCUS_FAILURE_PHRASE.NO_SUCH_TARGET_DESKTOP))
return FAILURE_REMEDY.START_SESSION;
return FAILURE_REMEDY.RETRY;
}
Loading