swarm-ui: only the newest start's late stop ends a claude login
This commit is contained in:
parent
41cc2f5e8e
commit
c2f17799fc
3 changed files with 48 additions and 8 deletions
|
|
@ -16,7 +16,9 @@ import { TextField } from "../../ui/text-field/TextField.js";
|
||||||
import {
|
import {
|
||||||
afterCode,
|
afterCode,
|
||||||
afterStart,
|
afterStart,
|
||||||
|
beginStart,
|
||||||
claudeLoginPath,
|
claudeLoginPath,
|
||||||
|
isLatestStart,
|
||||||
leavesLoginRunning,
|
leavesLoginRunning,
|
||||||
pastedCode,
|
pastedCode,
|
||||||
startOutlivedDialog,
|
startOutlivedDialog,
|
||||||
|
|
@ -40,11 +42,12 @@ export function ClaudeLoginDialog({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let live = true;
|
let live = true;
|
||||||
|
const mine = beginStart(agent);
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const r = await fetch(claudeLoginPath(agent), { method: "POST" });
|
const r = await fetch(claudeLoginPath(agent), { method: "POST" });
|
||||||
const problem = await problemOf(r);
|
const problem = await problemOf(r);
|
||||||
if (startOutlivedDialog(live, problem)) {
|
if (startOutlivedDialog(live, problem, isLatestStart(agent, mine))) {
|
||||||
void fetch(claudeLoginPath(agent), { method: "DELETE" });
|
void fetch(claudeLoginPath(agent), { method: "DELETE" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,14 +34,33 @@ export function leavesLoginRunning(s: LoginStep): boolean {
|
||||||
return s.step === "code";
|
return s.step === "code";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The newest start per agent. The minter keeps one login per agent and a
|
||||||
|
// new start replaces it, so a late stop from an older start would end the
|
||||||
|
// newer dialog's login.
|
||||||
|
const latestStart = new Map<string, number>();
|
||||||
|
|
||||||
|
// Marks a start as the agent's newest and returns its number.
|
||||||
|
export function beginStart(agent: string): number {
|
||||||
|
const n = (latestStart.get(agent) ?? 0) + 1;
|
||||||
|
latestStart.set(agent, n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether `n` is still the agent's newest start.
|
||||||
|
export function isLatestStart(agent: string, n: number): boolean {
|
||||||
|
return latestStart.get(agent) === n;
|
||||||
|
}
|
||||||
|
|
||||||
// Whether a start that answered after the dialog went away left a login
|
// Whether a start that answered after the dialog went away left a login
|
||||||
// running: it did unless the start itself failed. The dialog stops it then,
|
// running that nothing else owns: it did unless the start itself failed or a
|
||||||
// since nothing is left to.
|
// newer start has replaced it. The dialog stops it then, since nothing is
|
||||||
|
// left to.
|
||||||
export function startOutlivedDialog(
|
export function startOutlivedDialog(
|
||||||
dialogLive: boolean,
|
dialogLive: boolean,
|
||||||
problem: ProblemDetails | null,
|
problem: ProblemDetails | null,
|
||||||
|
latest: boolean,
|
||||||
): boolean {
|
): boolean {
|
||||||
return !dialogLive && problem === null;
|
return !dialogLive && problem === null && latest;
|
||||||
}
|
}
|
||||||
|
|
||||||
// What the code form may submit: the pasted code without surrounding
|
// What the code form may submit: the pasted code without surrounding
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ import { test } from "node:test";
|
||||||
import {
|
import {
|
||||||
afterCode,
|
afterCode,
|
||||||
afterStart,
|
afterStart,
|
||||||
|
beginStart,
|
||||||
claudeLoginPath,
|
claudeLoginPath,
|
||||||
|
isLatestStart,
|
||||||
leavesLoginRunning,
|
leavesLoginRunning,
|
||||||
pastedCode,
|
pastedCode,
|
||||||
startOutlivedDialog,
|
startOutlivedDialog,
|
||||||
|
|
@ -49,10 +51,26 @@ test("only a login waiting for its code is stopped on close", () => {
|
||||||
assert.equal(leavesLoginRunning({ step: "stored" }), false);
|
assert.equal(leavesLoginRunning({ step: "stored" }), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("a start that answers after the dialog closed is stopped, unless it failed", () => {
|
test("a start that answers after the dialog closed is stopped, unless it failed or was replaced", () => {
|
||||||
assert.equal(startOutlivedDialog(false, null), true);
|
assert.equal(startOutlivedDialog(false, null, true), true);
|
||||||
assert.equal(startOutlivedDialog(true, null), false);
|
assert.equal(startOutlivedDialog(true, null, true), false);
|
||||||
assert.equal(startOutlivedDialog(false, { detail: "no minter" }), false);
|
assert.equal(
|
||||||
|
startOutlivedDialog(false, { detail: "no minter" }, true),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert.equal(startOutlivedDialog(false, null, false), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("only an agent's newest start counts as the latest", () => {
|
||||||
|
const first = beginStart("login-test-a");
|
||||||
|
assert.equal(isLatestStart("login-test-a", first), true);
|
||||||
|
const second = beginStart("login-test-a");
|
||||||
|
assert.equal(isLatestStart("login-test-a", first), false);
|
||||||
|
assert.equal(isLatestStart("login-test-a", second), true);
|
||||||
|
// Another agent's starts are counted on their own.
|
||||||
|
const other = beginStart("login-test-b");
|
||||||
|
assert.equal(isLatestStart("login-test-a", second), true);
|
||||||
|
assert.equal(isLatestStart("login-test-b", other), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("a pasted code is trimmed, and a blank one is nothing", () => {
|
test("a pasted code is trimmed, and a blank one is nothing", () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue