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', () => {