fix(local-model): name a session without a name in the "still using this model" refusal

Seen on a MacBook (M5 Pro, 2026-11-12): with a worker of the codeman skill open on the
local model, Stop, Remove and a switch of models answered

    Sessions are still using this model: . Close them first.

A session an agent starts has no name of its own (a quick-start without sessionName), and
the refusal listed session.name. The tab of such a session shows the last folder of its
working directory, else the first eight characters of its id, so the sentence now uses the
same label (sessionLabel() beside sessionNamesUsingEndpoint()).

The sentence has shipped since 1.50.0 for Stop and Remove. This branch adds it to Use and
to the install of another model, which is where a person with local workers open meets it.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-11-12 00:08:59 +01:00
1 parent 7bdef0d86a
commit db81d55fc9
2 files changed
+42 -4

No files matched your search

+18 -4
View File
@@ -263,16 +263,30 @@ export async function isManagedLocalEndpoint(id: string): Promise<boolean> {
}
/**
* The names of the live sessions that use the endpoint `endpointId`, in the order given.
* Pure. What `DELETE /api/local-model` answers its 409 with.
* What a session is called where a person reads it. A session an agent started (a worker of
* the codeman skill, `codeman agent spawn`) has no name of its own, and its tab then shows
* the last folder of its working directory, else the first eight characters of its id
* (`getSessionName()` in app.js). The same steps here, so that a sentence which lists
* sessions never lists an empty name. Pure.
*/
export function sessionLabel(session: { id?: string; name?: string; workingDir?: string }): string {
const name = (session.name ?? '').trim();
if (name) return name;
const folder = (session.workingDir ?? '').split('/').filter(Boolean).pop();
return folder || (session.id ?? '').slice(0, 8);
}
/**
* The live sessions that use the endpoint `endpointId`, in the order given, each by the name
* its tab shows (`sessionLabel()`). Pure. What the feature answers a 409 `sessions-open` with.
*/
export function sessionNamesUsingEndpoint(
sessions: Iterable<{ name: string; customModel?: { endpointId: string } }>,
sessions: Iterable<{ id?: string; name: string; workingDir?: string; customModel?: { endpointId: string } }>,
endpointId: string = LOCAL_MODEL_ENDPOINT_ID
): string[] {
const names: string[] = [];
for (const session of sessions) {
if (session.customModel?.endpointId === endpointId) names.push(session.name);
if (session.customModel?.endpointId === endpointId) names.push(sessionLabel(session));
}
return names;
}
+24
View File
@@ -375,6 +375,30 @@ describe('sessionNamesUsingEndpoint', () => {
expect(sessionNamesUsingEndpoint(new Set(sessions), 'office-gpu')).toEqual(['w3-lib']);
expect(sessionNamesUsingEndpoint(new Map().values())).toEqual([]);
});
// Seen on a Mac: a worker the codeman skill started has no name, and the refusal read
// "Sessions are still using this model: . Close them first."
it('names a session without a name the way its tab does: the folder, else the short id', () => {
const ours = { endpointId: 'local-ollama' };
expect(
sessionNamesUsingEndpoint([
{
id: '7dbd7b0d-0000-4000-8000-000000000001',
name: '',
workingDir: '/Users/x/codeman-cases/summaries',
customModel: ours,
},
{
id: 'a030dddb-0000-4000-8000-000000000002',
name: ' ',
workingDir: '/Users/x/codeman-cases/review/',
customModel: ours,
},
{ id: 'c1d2e3f4-0000-4000-8000-000000000003', name: '', workingDir: '', customModel: ours },
{ id: 'ffffffff-0000-4000-8000-000000000004', name: 'w1-app', workingDir: '/Users/x/app', customModel: ours },
])
).toEqual(['summaries', 'review', 'c1d2e3f4', 'w1-app']);
});
});
describe('compareModelDigest', () => {