From db81d55fc9b909d61b0fc861f33d7ce3b2df6af5 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Thu, 12 Nov 2026 00:08:59 +0100 Subject: [PATCH] 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 --- src/local-model/connect.ts | 22 ++++++++++++++++++---- test/local-model-connect.test.ts | 24 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/local-model/connect.ts b/src/local-model/connect.ts index 0af1fb12..d6d8e87b 100644 --- a/src/local-model/connect.ts +++ b/src/local-model/connect.ts @@ -263,16 +263,30 @@ export async function isManagedLocalEndpoint(id: string): Promise { } /** - * 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; } diff --git a/test/local-model-connect.test.ts b/test/local-model-connect.test.ts index 3630baf6..41d43e6f 100644 --- a/test/local-model-connect.test.ts +++ b/test/local-model-connect.test.ts @@ -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', () => {