feat(v0.3): H2 server — Claude Code hooks → live status side-channel
- types: ClaudeStatus + ServerMessage 'status'; Session.claudeStatus; SessionManager.handleHookEvent - session: inject WEBTERM_SESSION + WEBTERM_HOOK_URL into the spawned shell env so hooks know which tab they belong to - http/hook.ts: pure event→status mapper (PreToolUse→working, PermissionRequest/ Notification:permission_prompt→waiting, Stop/idle_prompt→idle); 6 unit tests - server: POST /hook (loopback-only, express.json 64kb) → manager pushes 'status' - fix: closeIdleConnections() on shutdown so an idle hook keep-alive socket doesn't hang close()/SIGTERM - integration ⑦ (real PTY): attach → POST /hook → ws receives status. 205 tests green.
This commit is contained in:
47
test/hook.test.ts
Normal file
47
test/hook.test.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { parseHookEvent } from '../src/http/hook.js'
|
||||
|
||||
const SID = 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
|
||||
|
||||
describe('parseHookEvent', () => {
|
||||
it('maps tool events to "working"', () => {
|
||||
for (const e of ['PreToolUse', 'PostToolUse', 'UserPromptSubmit']) {
|
||||
expect(parseHookEvent(SID, { hook_event_name: e })).toEqual({
|
||||
sessionId: SID,
|
||||
status: 'working',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('maps PermissionRequest to "waiting" with the tool name as detail', () => {
|
||||
expect(parseHookEvent(SID, { hook_event_name: 'PermissionRequest', tool_name: 'Bash' })).toEqual(
|
||||
{ sessionId: SID, status: 'waiting', detail: 'Bash' },
|
||||
)
|
||||
})
|
||||
|
||||
it('maps Notification permission_prompt to "waiting", idle_prompt to "idle"', () => {
|
||||
expect(
|
||||
parseHookEvent(SID, { hook_event_name: 'Notification', notification_type: 'permission_prompt' }),
|
||||
).toEqual({ sessionId: SID, status: 'waiting' })
|
||||
expect(
|
||||
parseHookEvent(SID, { hook_event_name: 'Notification', notification_type: 'idle_prompt' }),
|
||||
).toEqual({ sessionId: SID, status: 'idle' })
|
||||
})
|
||||
|
||||
it('maps Stop / SessionEnd to "idle"', () => {
|
||||
expect(parseHookEvent(SID, { hook_event_name: 'Stop' })).toEqual({ sessionId: SID, status: 'idle' })
|
||||
expect(parseHookEvent(SID, { hook_event_name: 'SessionEnd' })).toEqual({
|
||||
sessionId: SID,
|
||||
status: 'idle',
|
||||
})
|
||||
})
|
||||
|
||||
it('returns null for missing sessionId, bad body, or unknown event', () => {
|
||||
expect(parseHookEvent(undefined, { hook_event_name: 'Stop' })).toBeNull()
|
||||
expect(parseHookEvent('', { hook_event_name: 'Stop' })).toBeNull()
|
||||
expect(parseHookEvent(SID, null)).toBeNull()
|
||||
expect(parseHookEvent(SID, 'nope')).toBeNull()
|
||||
expect(parseHookEvent(SID, { hook_event_name: 'SomethingElse' })).toBeNull()
|
||||
expect(parseHookEvent(SID, {})).toBeNull()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user