@jacexh/claude-web-console 0.12.1 → 0.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacexh/claude-web-console",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "description": "Web-based console for Claude Code — manage sessions, switch models, preview artifacts",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { waitForSessionId } from '../session-id-resolver'
3
+
4
+ describe('waitForSessionId', () => {
5
+ it('resolves when session.sessionId becomes available', async () => {
6
+ let ready = false
7
+ const session = {
8
+ get sessionId() {
9
+ if (!ready) throw new Error('not ready')
10
+ return 'real-session-id'
11
+ },
12
+ }
13
+
14
+ // Simulate SDK resolving sessionId after 100ms
15
+ setTimeout(() => { ready = true }, 100)
16
+
17
+ const result = await waitForSessionId(session as any, 5000)
18
+ expect(result).toBe('real-session-id')
19
+ })
20
+
21
+ it('resolves immediately when sessionId is already available', async () => {
22
+ const session = { sessionId: 'already-ready' }
23
+ const result = await waitForSessionId(session as any, 5000)
24
+ expect(result).toBe('already-ready')
25
+ })
26
+
27
+ it('rejects on timeout if sessionId never becomes available', async () => {
28
+ const session = {
29
+ get sessionId(): string { throw new Error('not ready') },
30
+ }
31
+
32
+ await expect(waitForSessionId(session as any, 200)).rejects.toThrow('Session init timed out')
33
+ })
34
+
35
+ it('ignores pending- prefixed session IDs', async () => {
36
+ let callCount = 0
37
+ const session = {
38
+ get sessionId() {
39
+ callCount++
40
+ if (callCount < 3) return 'pending-123'
41
+ return 'real-id'
42
+ },
43
+ }
44
+
45
+ const result = await waitForSessionId(session as any, 5000)
46
+ expect(result).toBe('real-id')
47
+ })
48
+ })
@@ -17,10 +17,10 @@ export function registerHttpRoutes(app: FastifyInstance, sessionManager: Session
17
17
  await mkdir(cwd, { recursive: true })
18
18
  }
19
19
 
20
- const tempId = await sessionManager.createSession(body ?? undefined)
21
- const status = sessionManager.getSessionStatus(tempId)
20
+ const sessionId = await sessionManager.createSession(body ?? undefined)
21
+ const status = sessionManager.getSessionStatus(sessionId)
22
22
 
23
- reply.code(201).send({ sessionId: tempId, status })
23
+ reply.code(201).send({ sessionId, status })
24
24
  })
25
25
 
26
26
  app.post<{ Params: { id: string } }>('/api/sessions/:id/resume', async (request, reply) => {
@@ -0,0 +1,39 @@
1
+ import type { SDKSession } from '@anthropic-ai/claude-agent-sdk'
2
+
3
+ /**
4
+ * Polls session.sessionId until the SDK resolves the real ID.
5
+ * The SDK sets sessionId asynchronously after process init.
6
+ */
7
+ export function waitForSessionId(session: SDKSession, timeoutMs: number): Promise<string> {
8
+ return new Promise((resolve, reject) => {
9
+ const timeout = setTimeout(() => {
10
+ clearInterval(poll)
11
+ reject(new Error('Session init timed out'))
12
+ }, timeoutMs)
13
+
14
+ const poll = setInterval(() => {
15
+ try {
16
+ const id = session.sessionId
17
+ if (id && !id.startsWith('pending-')) {
18
+ clearInterval(poll)
19
+ clearTimeout(timeout)
20
+ resolve(id)
21
+ }
22
+ } catch {
23
+ // sessionId not ready yet, keep polling
24
+ }
25
+ }, 50)
26
+
27
+ // Check immediately (no need to wait 50ms if already ready)
28
+ try {
29
+ const id = session.sessionId
30
+ if (id && !id.startsWith('pending-')) {
31
+ clearInterval(poll)
32
+ clearTimeout(timeout)
33
+ resolve(id)
34
+ }
35
+ } catch {
36
+ // not ready, poll will handle it
37
+ }
38
+ })
39
+ }
@@ -19,6 +19,7 @@ import type { FastifyBaseLogger } from 'fastify'
19
19
  import type { SessionInfo, EffortLevel } from './types.js'
20
20
  import { shouldBroadcastTurnStarted, shouldResetToIdleOnStreamEnd, isTurnMessage, type TurnState } from './turn-lifecycle.js'
21
21
  import { SessionStatusTracker } from './session-status.js'
22
+ import { waitForSessionId } from './session-id-resolver.js'
22
23
 
23
24
  type PermissionResolver = {
24
25
  resolve: (approved: boolean, reason?: string, updatedPermissions?: import('@anthropic-ai/claude-agent-sdk').PermissionUpdate[]) => void
@@ -389,7 +390,9 @@ export class SessionManager {
389
390
  // For new sessions, start stream immediately — SDK may emit init messages
390
391
  this.startStreamConsumer(tempId, session)
391
392
 
392
- return tempId
393
+ // Poll session.sessionId directly — don't depend on consumeStream remap timing
394
+ const sessionId = await waitForSessionId(session, 30_000)
395
+ return sessionId
393
396
  }
394
397
 
395
398
  private fetchAndBroadcastModels(sessionId: string, session: SDKSession, currentModel?: string): void {