diff --git a/skill-src/webcmd-browser/SKILL.src.md b/skill-src/webcmd-browser/SKILL.src.md index 60de9aba..fe49c341 100644 --- a/skill-src/webcmd-browser/SKILL.src.md +++ b/skill-src/webcmd-browser/SKILL.src.md @@ -270,6 +270,7 @@ Use `run` and inspect `page.frames()`; target the frame by URL/name and keep ifr | `run` times out before returning | Increase `--timeout` only after checking whether the wait condition is wrong. | | Write may have happened before timeout | Take a fresh snapshot before retrying. Avoid duplicate submissions. | | `SESSION_REQUIRED` | Create a Session, then retry with root `--session `. | +| `SESSION_NOT_FOUND` | Pass the same `--profile` used on `session create`. `session list` is per profile. | | `SESSION_BUSY` | Wait for the listed holder; if it is dead, `webcmd session close --force` is the last resort. | | `SESSION_PAUSED_FOR_HUMAN_HANDOFF` | Finish the handoff and run the returned verifier before retrying. | | Login wall appears | Use the Authentication and human handoff recipe. | @@ -294,4 +295,7 @@ Author-only. Stripped by litprompt, so it costs the running agent nothing. Append one dated line whenever a correction lands, or whenever an approach is tried and rejected. Record what was tried and why it failed, not just what won. + +- 2026-08-20: `session close` without `--profile` said Session not found and + listed the default profile. Name the owning profile when the id exists elsewhere. --> diff --git a/skills/webcmd-browser/SKILL.md b/skills/webcmd-browser/SKILL.md index f972f5b7..c83de114 100644 --- a/skills/webcmd-browser/SKILL.md +++ b/skills/webcmd-browser/SKILL.md @@ -270,6 +270,7 @@ Use `run` and inspect `page.frames()`; target the frame by URL/name and keep ifr | `run` times out before returning | Increase `--timeout` only after checking whether the wait condition is wrong. | | Write may have happened before timeout | Take a fresh snapshot before retrying. Avoid duplicate submissions. | | `SESSION_REQUIRED` | Create a Session, then retry with root `--session `. | +| `SESSION_NOT_FOUND` | Pass the same `--profile` used on `session create`. `session list` is per profile. | | `SESSION_BUSY` | Wait for the listed holder; if it is dead, `webcmd session close --force` is the last resort. | | `SESSION_PAUSED_FOR_HUMAN_HANDOFF` | Finish the handoff and run the returned verifier before retrying. | | Login wall appears | Use the Authentication and human handoff recipe. | diff --git a/src/browser/sessions.test.ts b/src/browser/sessions.test.ts index 676a677d..f4ea9d0b 100644 --- a/src/browser/sessions.test.ts +++ b/src/browser/sessions.test.ts @@ -43,7 +43,14 @@ describe('LocalBrowserSessionStore', () => { const store = new LocalBrowserSessionStore({ baseDir: tempDir(), idFactory: () => 'session_a' }); const created = store.create('profile_work'); - expect(() => store.require('profile_other', created.id)).toThrowError(expect.objectContaining({ code: 'SESSION_NOT_FOUND' })); + expect(() => store.require('profile_other', created.id)).toThrowError(expect.objectContaining({ + code: 'SESSION_NOT_FOUND', + hint: expect.stringContaining('webcmd --profile profile_work session close'), + })); + expect(() => store.require('profile_missing', 'session_zzzzzzzz-zzzz-4zzz-8zzz-zzzzzzzzzzzz')).toThrowError(expect.objectContaining({ + code: 'SESSION_NOT_FOUND', + hint: expect.stringContaining('Sessions are per profile'), + })); expect(() => store.find('profile_work', 'work')).toThrowError(expect.objectContaining({ code: 'INVALID_SESSION_SELECTOR' })); }); diff --git a/src/browser/sessions.ts b/src/browser/sessions.ts index d046999b..92e1f247 100644 --- a/src/browser/sessions.ts +++ b/src/browser/sessions.ts @@ -30,11 +30,14 @@ type StateFile = { version: 1; sessions: BrowserSessionRecord[] }; const SESSION_RETENTION_MS = 30 * 24 * 60 * 60 * 1000; export class SessionNotFoundError extends CliError { - constructor(sessionId: string, profileId: string) { + constructor(sessionId: string, profileId: string, ownerProfileId?: string) { + const hint = ownerProfileId && ownerProfileId !== profileId + ? `This Session belongs to profile ${ownerProfileId}. Retry with \`webcmd --profile ${ownerProfileId} session close ${sessionId}\`. List with \`webcmd --profile ${ownerProfileId} session list\`.` + : `Sessions are per profile. Run \`webcmd --profile ${profileId} session list\` or \`webcmd profile list\`, then pass the same \`--profile\` used on create.`; super( 'SESSION_NOT_FOUND', `Session not found: ${sessionId}`, - `Run \`webcmd --profile ${profileId} session list\` to choose an existing Session.`, + hint, EXIT_CODES.EMPTY_RESULT, ); } @@ -84,7 +87,10 @@ export class LocalBrowserSessionStore { requireSessionIdShape(id); const state = this.load(); const record = state.sessions.find((row) => row.id === id && row.profileId === profileId); - if (!record) throw new SessionNotFoundError(id, profileId); + if (!record) { + const owner = state.sessions.find((row) => row.id === id); + throw new SessionNotFoundError(id, profileId, owner?.profileId); + } this.touchRecord(state, record); return { ...record }; } @@ -176,7 +182,10 @@ export class LocalBrowserSessionStore { private requireMutable(state: StateFile, profileId: string, sessionId: string): BrowserSessionRecord { requireSessionIdShape(sessionId); const record = state.sessions.find((row) => row.id === sessionId && row.profileId === profileId); - if (!record) throw new SessionNotFoundError(sessionId, profileId); + if (!record) { + const owner = state.sessions.find((row) => row.id === sessionId); + throw new SessionNotFoundError(sessionId, profileId, owner?.profileId); + } return record; } diff --git a/src/cli.ts b/src/cli.ts index ac826330..d0fbedea 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -876,7 +876,7 @@ export function createProgram(BUILTIN_CLIS: string, USER_CLIS: string, pluginsDi console.log(`No browser Sessions found for Profile ${profileId}.`); return; } - await renderOutput(output, { fmt, fmtExplicit: command.getOptionValueSource('format') === 'cli', columns: ['id', 'kind', 'runtimeState', 'handoff'] }); + await renderOutput(output, { fmt, fmtExplicit: command.getOptionValueSource('format') === 'cli', columns: ['id', 'kind', 'profileId', 'runtimeState', 'handoff'] }); }); sessionCmd diff --git a/src/skills.test.ts b/src/skills.test.ts index bfe8b5c8..9ce73d39 100644 --- a/src/skills.test.ts +++ b/src/skills.test.ts @@ -288,6 +288,7 @@ describe('webcmd skills content', () => { expect(browser).toMatch(/Profiles are cookie jars[\s\S]{0,180}sessions are browser workspaces\/windows/i); expect(browser).toMatch(/Parallel agents use separate sessions/i); expect(browser).toContain('SESSION_BUSY'); + expect(browser).toContain('SESSION_NOT_FOUND'); expect(browser).toContain('SESSION_PAUSED_FOR_HUMAN_HANDOFF'); expect(browser).toContain('webcmd session close --force'); for (const skill of [usage, browser, autofix]) {