@jacexh/claude-web-console 0.11.1 → 0.11.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
|
@@ -11,7 +11,7 @@ import { describe, it, expect } from 'vitest'
|
|
|
11
11
|
* We extract the logic into a pure function so it can be tested
|
|
12
12
|
* without mocking the full SessionManager.
|
|
13
13
|
*/
|
|
14
|
-
import { shouldBroadcastTurnStarted } from '../turn-lifecycle'
|
|
14
|
+
import { shouldBroadcastTurnStarted, shouldResetToIdleOnStreamEnd, isTurnMessage } from '../turn-lifecycle'
|
|
15
15
|
|
|
16
16
|
describe('shouldBroadcastTurnStarted', () => {
|
|
17
17
|
it('returns true for the first message of a turn', () => {
|
|
@@ -51,3 +51,51 @@ describe('shouldBroadcastTurnStarted', () => {
|
|
|
51
51
|
expect(shouldBroadcastTurnStarted(state)).toBe(false)
|
|
52
52
|
})
|
|
53
53
|
})
|
|
54
|
+
|
|
55
|
+
describe('shouldResetToIdleOnStreamEnd', () => {
|
|
56
|
+
it('returns true when current status is running (stream ended without result)', () => {
|
|
57
|
+
expect(shouldResetToIdleOnStreamEnd('running')).toBe(true)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('returns false when current status is idle (result already set idle)', () => {
|
|
61
|
+
expect(shouldResetToIdleOnStreamEnd('idle')).toBe(false)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('returns false when current status is stopped (session closed)', () => {
|
|
65
|
+
expect(shouldResetToIdleOnStreamEnd('stopped')).toBe(false)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('isTurnMessage', () => {
|
|
70
|
+
it('returns false for system/init (session initialization, not a turn)', () => {
|
|
71
|
+
expect(isTurnMessage({ type: 'system', subtype: 'init' })).toBe(false)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('returns true for assistant messages (agent generating)', () => {
|
|
75
|
+
expect(isTurnMessage({ type: 'assistant' })).toBe(true)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('returns true for user messages (tool results)', () => {
|
|
79
|
+
expect(isTurnMessage({ type: 'user' })).toBe(true)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('returns true for result messages (turn complete)', () => {
|
|
83
|
+
expect(isTurnMessage({ type: 'result' })).toBe(true)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('returns true for system/task_started', () => {
|
|
87
|
+
expect(isTurnMessage({ type: 'system', subtype: 'task_started' })).toBe(true)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('returns true for system/task_progress', () => {
|
|
91
|
+
expect(isTurnMessage({ type: 'system', subtype: 'task_progress' })).toBe(true)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('returns true for system/task_notification', () => {
|
|
95
|
+
expect(isTurnMessage({ type: 'system', subtype: 'task_notification' })).toBe(true)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('returns false for system/init even with extra fields', () => {
|
|
99
|
+
expect(isTurnMessage({ type: 'system', subtype: 'init', model: 'claude-sonnet' })).toBe(false)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
@@ -17,7 +17,7 @@ import { join } from 'node:path'
|
|
|
17
17
|
import { homedir } from 'node:os'
|
|
18
18
|
import type { FastifyBaseLogger } from 'fastify'
|
|
19
19
|
import type { SessionInfo, EffortLevel } from './types.js'
|
|
20
|
-
import { shouldBroadcastTurnStarted, type TurnState } from './turn-lifecycle.js'
|
|
20
|
+
import { shouldBroadcastTurnStarted, shouldResetToIdleOnStreamEnd, isTurnMessage, type TurnState } from './turn-lifecycle.js'
|
|
21
21
|
import { SessionStatusTracker } from './session-status.js'
|
|
22
22
|
|
|
23
23
|
type PermissionResolver = {
|
|
@@ -537,8 +537,8 @@ export class SessionManager {
|
|
|
537
537
|
} as unknown as SDKMessage))
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
-
// Set session status to running on first message
|
|
541
|
-
if (shouldBroadcastTurnStarted(turnState)) {
|
|
540
|
+
// Set session status to running on first turn message (skip system/init)
|
|
541
|
+
if (isTurnMessage(msgAny) && shouldBroadcastTurnStarted(turnState)) {
|
|
542
542
|
this.sessionStatus.set(sessionId, 'running')
|
|
543
543
|
}
|
|
544
544
|
this.broadcast(sessionId, (l) => l.onMessage(sessionId, msg))
|
|
@@ -549,7 +549,7 @@ export class SessionManager {
|
|
|
549
549
|
try { sessionId = session.sessionId } catch { break }
|
|
550
550
|
if (this.closedSessionIds.has(sessionId)) break
|
|
551
551
|
// Reset to idle if stream ended without a result (e.g. init-only stream)
|
|
552
|
-
if (this.sessionStatus.get(sessionId)
|
|
552
|
+
if (shouldResetToIdleOnStreamEnd(this.sessionStatus.get(sessionId))) {
|
|
553
553
|
this.sessionStatus.set(sessionId, 'idle')
|
|
554
554
|
}
|
|
555
555
|
// Wait briefly before re-entering stream() for next turn
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface TurnState {
|
|
2
|
+
turnStarted: boolean
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/** Returns true (and flips the flag) on the first call per turn. */
|
|
6
|
+
export function shouldBroadcastTurnStarted(state: TurnState): boolean {
|
|
7
|
+
if (state.turnStarted) return false
|
|
8
|
+
state.turnStarted = true
|
|
9
|
+
return true
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Returns true if status should be reset to idle when a stream ends without a result. */
|
|
13
|
+
export function shouldResetToIdleOnStreamEnd(currentStatus: string): boolean {
|
|
14
|
+
return currentStatus === 'running'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Returns true if this SDK message is part of an active turn (not session init). */
|
|
18
|
+
export function isTurnMessage(msg: Record<string, unknown>): boolean {
|
|
19
|
+
if (msg.type === 'system' && msg.subtype === 'init') return false
|
|
20
|
+
return true
|
|
21
|
+
}
|