@jacexh/claude-web-console 0.10.6 → 0.11.1

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.
Files changed (64) hide show
  1. package/client/dist/assets/{_baseUniq-68MegVue.js → _baseUniq-C8KadaZm.js} +1 -1
  2. package/client/dist/assets/{arc-DGs2oxvI.js → arc-B2W9x-cV.js} +1 -1
  3. package/client/dist/assets/{architectureDiagram-Q4EWVU46-9f3V68WI.js → architectureDiagram-Q4EWVU46-BRjvIRM_.js} +1 -1
  4. package/client/dist/assets/{blockDiagram-DXYQGD6D-nw_XrJPn.js → blockDiagram-DXYQGD6D-BcTkMEef.js} +1 -1
  5. package/client/dist/assets/{c4Diagram-AHTNJAMY-DtmpkbCH.js → c4Diagram-AHTNJAMY-egjSISEz.js} +1 -1
  6. package/client/dist/assets/channel-De3V3xku.js +1 -0
  7. package/client/dist/assets/{chunk-4BX2VUAB-CXkBrbXl.js → chunk-4BX2VUAB-5ky7wtNG.js} +1 -1
  8. package/client/dist/assets/{chunk-4TB4RGXK-Cpwuap5g.js → chunk-4TB4RGXK-CT-lH0qn.js} +1 -1
  9. package/client/dist/assets/{chunk-55IACEB6-MDkMv1Vj.js → chunk-55IACEB6-D9CKt7OU.js} +1 -1
  10. package/client/dist/assets/{chunk-EDXVE4YY-fWFZkL3K.js → chunk-EDXVE4YY-NwC01K04.js} +1 -1
  11. package/client/dist/assets/{chunk-FMBD7UC4-aLTS4ppS.js → chunk-FMBD7UC4-BCG3_zqC.js} +1 -1
  12. package/client/dist/assets/{chunk-OYMX7WX6-CdiOjMj9.js → chunk-OYMX7WX6-DJSGZ6jV.js} +1 -1
  13. package/client/dist/assets/{chunk-QZHKN3VN-B9w_-JI6.js → chunk-QZHKN3VN-BBIVA3HS.js} +1 -1
  14. package/client/dist/assets/{chunk-YZCP3GAM-BCHcb4hM.js → chunk-YZCP3GAM-C09sqCat.js} +1 -1
  15. package/client/dist/assets/classDiagram-6PBFFD2Q-CqsWJaxt.js +1 -0
  16. package/client/dist/assets/classDiagram-v2-HSJHXN6E-CqsWJaxt.js +1 -0
  17. package/client/dist/assets/clone-5Qb140gW.js +1 -0
  18. package/client/dist/assets/{cose-bilkent-S5V4N54A-D-D1pyZ4.js → cose-bilkent-S5V4N54A-35P9iw0b.js} +1 -1
  19. package/client/dist/assets/{dagre-KV5264BT-6Cz3n9So.js → dagre-KV5264BT-CXV-WCZD.js} +1 -1
  20. package/client/dist/assets/{diagram-5BDNPKRD-DFjJzsE0.js → diagram-5BDNPKRD-Dp3u_O_t.js} +1 -1
  21. package/client/dist/assets/{diagram-G4DWMVQ6-BS9PCHKv.js → diagram-G4DWMVQ6-BtWAfxpb.js} +1 -1
  22. package/client/dist/assets/{diagram-MMDJMWI5-CsF9E5G3.js → diagram-MMDJMWI5-hf_41SmM.js} +1 -1
  23. package/client/dist/assets/{diagram-TYMM5635-CZ413KNX.js → diagram-TYMM5635-BjK1YnVW.js} +1 -1
  24. package/client/dist/assets/{erDiagram-SMLLAGMA-WO8fXuNp.js → erDiagram-SMLLAGMA-CmcBJzib.js} +1 -1
  25. package/client/dist/assets/{flowDiagram-DWJPFMVM-CzBUR3uU.js → flowDiagram-DWJPFMVM-D90oEHbY.js} +1 -1
  26. package/client/dist/assets/{ganttDiagram-T4ZO3ILL-B60qcaB5.js → ganttDiagram-T4ZO3ILL-BkpoWXoe.js} +1 -1
  27. package/client/dist/assets/{gitGraphDiagram-UUTBAWPF-CnB_Lftl.js → gitGraphDiagram-UUTBAWPF-C8zLE_7k.js} +1 -1
  28. package/client/dist/assets/{graph-CwsRpftd.js → graph-Bh3K-muc.js} +1 -1
  29. package/client/dist/assets/{index-BlOeGePC.js → index-DBmP29uk.js} +77 -77
  30. package/client/dist/assets/{infoDiagram-42DDH7IO-CPfOWmw3.js → infoDiagram-42DDH7IO-BM0WXxrt.js} +1 -1
  31. package/client/dist/assets/{ishikawaDiagram-UXIWVN3A-DbLJUVid.js → ishikawaDiagram-UXIWVN3A-UAHaEHNp.js} +1 -1
  32. package/client/dist/assets/{journeyDiagram-VCZTEJTY-Dbl8zh4b.js → journeyDiagram-VCZTEJTY-Cafrpd2t.js} +1 -1
  33. package/client/dist/assets/{kanban-definition-6JOO6SKY-Csb1o0yW.js → kanban-definition-6JOO6SKY-D0ZRwwaM.js} +1 -1
  34. package/client/dist/assets/{layout-BK4Q6R8I.js → layout-D9SZ1QBf.js} +1 -1
  35. package/client/dist/assets/{linear-CvJiszRa.js → linear-CpQg9EBQ.js} +1 -1
  36. package/client/dist/assets/{mermaid.core-B3-wKFnz.js → mermaid.core-Cff27cfj.js} +4 -4
  37. package/client/dist/assets/{min-D5RjKPqt.js → min-CCDTUlLD.js} +1 -1
  38. package/client/dist/assets/{mindmap-definition-QFDTVHPH-HPPx8VRM.js → mindmap-definition-QFDTVHPH-1_xvkSC-.js} +1 -1
  39. package/client/dist/assets/{pieDiagram-DEJITSTG-BBd6S7qX.js → pieDiagram-DEJITSTG-CXJh-BmH.js} +1 -1
  40. package/client/dist/assets/{quadrantDiagram-34T5L4WZ-5mFLAEO_.js → quadrantDiagram-34T5L4WZ-IIET-zJK.js} +1 -1
  41. package/client/dist/assets/{requirementDiagram-MS252O5E-D_j8Btvg.js → requirementDiagram-MS252O5E-CydtzCWy.js} +1 -1
  42. package/client/dist/assets/{sankeyDiagram-XADWPNL6-D-mJMVBg.js → sankeyDiagram-XADWPNL6-ByQ8OATo.js} +1 -1
  43. package/client/dist/assets/{sequenceDiagram-FGHM5R23-Du_HXzIS.js → sequenceDiagram-FGHM5R23-D1qYShIQ.js} +1 -1
  44. package/client/dist/assets/{stateDiagram-FHFEXIEX-DK1zMyaT.js → stateDiagram-FHFEXIEX-CLldWrCV.js} +1 -1
  45. package/client/dist/assets/stateDiagram-v2-QKLJ7IA2-B5cjRsZb.js +1 -0
  46. package/client/dist/assets/{timeline-definition-GMOUNBTQ-C4sXzll7.js → timeline-definition-GMOUNBTQ-DnPj25Y5.js} +1 -1
  47. package/client/dist/assets/{vennDiagram-DHZGUBPP-CxaV9frx.js → vennDiagram-DHZGUBPP-CnaH-R-v.js} +1 -1
  48. package/client/dist/assets/{wardley-RL74JXVD-Ci_gt1QK.js → wardley-RL74JXVD-D-Yc5POx.js} +1 -1
  49. package/client/dist/assets/{wardleyDiagram-NUSXRM2D-BVBkkfaG.js → wardleyDiagram-NUSXRM2D-BqXhvnsn.js} +1 -1
  50. package/client/dist/assets/{xychartDiagram-5P7HB3ND-Dgg3kv43.js → xychartDiagram-5P7HB3ND--56nlnda.js} +1 -1
  51. package/client/dist/index.html +1 -1
  52. package/package.json +1 -1
  53. package/server/src/__tests__/session-status-forward.test.ts +39 -0
  54. package/server/src/__tests__/session-status.test.ts +71 -0
  55. package/server/src/__tests__/turn-started-broadcast.test.ts +53 -0
  56. package/server/src/session-manager.ts +29 -9
  57. package/server/src/session-status.ts +30 -0
  58. package/server/src/types.ts +9 -1
  59. package/server/src/ws-handler.ts +6 -0
  60. package/client/dist/assets/channel-C81EZqNp.js +0 -1
  61. package/client/dist/assets/classDiagram-6PBFFD2Q-Bwl-Yyn3.js +0 -1
  62. package/client/dist/assets/classDiagram-v2-HSJHXN6E-Bwl-Yyn3.js +0 -1
  63. package/client/dist/assets/clone-DwEzo7pK.js +0 -1
  64. package/client/dist/assets/stateDiagram-v2-QKLJ7IA2-BHkgOSON.js +0 -1
@@ -17,6 +17,8 @@ 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'
21
+ import { SessionStatusTracker } from './session-status.js'
20
22
 
21
23
  type PermissionResolver = {
22
24
  resolve: (approved: boolean, reason?: string, updatedPermissions?: import('@anthropic-ai/claude-agent-sdk').PermissionUpdate[]) => void
@@ -147,7 +149,11 @@ export class SessionManager {
147
149
  private sessions = new Map<string, SDKSession>()
148
150
  private pendingPermissions = new Map<string, PermissionResolver>()
149
151
  private pendingElicitations = new Map<string, { resolve: (result: ElicitationResult) => void; sessionId: string }>()
150
- private runningSessionIds = new Set<string>()
152
+ private sessionStatus = new SessionStatusTracker((sessionId, status) => {
153
+ this.broadcast(sessionId, (l) => l.onMessage(sessionId, {
154
+ type: 'session_status', sessionId, status,
155
+ } as unknown as SDKMessage))
156
+ })
151
157
  private closedSessionIds = new Set<string>()
152
158
  private streamingSessionIds = new Set<string>()
153
159
  private sessionListeners = new Map<string, Set<SessionListener>>()
@@ -368,6 +374,7 @@ export class SessionManager {
368
374
  // Use a temporary ID, then remap when real sessionId arrives.
369
375
  const tempId = `pending-${Date.now()}`
370
376
  this.sessions.set(tempId, session)
377
+ this.sessionStatus.set(tempId, 'idle')
371
378
  this.sessionCwds.set(tempId, cwd)
372
379
  this.sessionCreationOptions.set(tempId, {
373
380
  model: options?.model,
@@ -425,6 +432,7 @@ export class SessionManager {
425
432
  while (true) {
426
433
  this.log.info({ sessionId: currentSessionId }, 'consumeStream: entering stream()')
427
434
  const query = session.stream()
435
+ const turnState: TurnState = { turnStarted: false }
428
436
  // Store query reference so control requests (setModel etc.) can use it
429
437
  try {
430
438
  const sid = session.sessionId
@@ -489,6 +497,7 @@ export class SessionManager {
489
497
  } else {
490
498
  this.log.info({ cost: (msgAny as Record<string, unknown>).total_cost_usd }, 'Turn complete')
491
499
  }
500
+ this.sessionStatus.set(currentSessionId, 'idle')
492
501
  }
493
502
 
494
503
  let sessionId: string
@@ -514,6 +523,9 @@ export class SessionManager {
514
523
  if (listeners) { this.sessionListeners.delete(tempId); this.sessionListeners.set(sessionId, listeners) }
515
524
  this.streamingSessionIds.delete(tempId)
516
525
  this.streamingSessionIds.add(sessionId)
526
+ const prevStatus = this.sessionStatus.get(tempId)
527
+ this.sessionStatus.delete(tempId)
528
+ if (prevStatus) this.sessionStatus.set(sessionId, prevStatus)
517
529
  const q = this.activeQueries.get(tempId)
518
530
  if (q) { this.activeQueries.delete(tempId); this.activeQueries.set(sessionId, q) }
519
531
  this.pendingRemaps.delete(tempId)
@@ -525,7 +537,10 @@ export class SessionManager {
525
537
  } as unknown as SDKMessage))
526
538
  }
527
539
 
528
- this.runningSessionIds.add(sessionId)
540
+ // Set session status to running on first message of each turn
541
+ if (shouldBroadcastTurnStarted(turnState)) {
542
+ this.sessionStatus.set(sessionId, 'running')
543
+ }
529
544
  this.broadcast(sessionId, (l) => l.onMessage(sessionId, msg))
530
545
  }
531
546
  this.log.info({ sessionId: currentSessionId }, 'consumeStream: stream() ended (turn complete)')
@@ -533,6 +548,10 @@ export class SessionManager {
533
548
  let sessionId: string
534
549
  try { sessionId = session.sessionId } catch { break }
535
550
  if (this.closedSessionIds.has(sessionId)) break
551
+ // Reset to idle if stream ended without a result (e.g. init-only stream)
552
+ if (this.sessionStatus.get(sessionId) === 'running') {
553
+ this.sessionStatus.set(sessionId, 'idle')
554
+ }
536
555
  // Wait briefly before re-entering stream() for next turn
537
556
  await new Promise((r) => setTimeout(r, 50))
538
557
  }
@@ -544,12 +563,12 @@ export class SessionManager {
544
563
  } finally {
545
564
  try {
546
565
  const sessionId = session.sessionId
547
- this.runningSessionIds.delete(sessionId)
566
+ this.sessionStatus.set(sessionId, 'stopped')
548
567
  this.streamingSessionIds.delete(sessionId)
549
568
  this.activeQueries.delete(sessionId)
550
569
  // Clean up stale session object if stream ended unexpectedly (not via explicit closeSession).
551
570
  // Without this, the session stays in this.sessions (appears "already running")
552
- // while runningSessionIds shows it as idle — making resume impossible.
571
+ // while sessionStatus shows it as idle — making resume impossible.
553
572
  if (this.sessions.has(sessionId) && !this.closedSessionIds.has(sessionId)) {
554
573
  const s = this.sessions.get(sessionId)
555
574
  this.sessions.delete(sessionId)
@@ -558,7 +577,7 @@ export class SessionManager {
558
577
  this.broadcast(sessionId, (l) => l.onEnd(sessionId))
559
578
  } catch {
560
579
  // Session never initialized — try with our tracked id
561
- this.runningSessionIds.delete(currentSessionId)
580
+ this.sessionStatus.set(currentSessionId, 'stopped')
562
581
  this.streamingSessionIds.delete(currentSessionId)
563
582
  this.activeQueries.delete(currentSessionId)
564
583
  if (this.sessions.has(currentSessionId) && !this.closedSessionIds.has(currentSessionId)) {
@@ -642,10 +661,11 @@ export class SessionManager {
642
661
  } as unknown as SDKMessage))
643
662
  }
644
663
 
645
- getSessionState(sessionId: string): { model?: string; effortLevel?: EffortLevel } {
664
+ getSessionState(sessionId: string): { model?: string; effortLevel?: EffortLevel; status: 'idle' | 'running' | 'stopped' } {
646
665
  return {
647
666
  model: this.sessionModels.get(sessionId),
648
667
  effortLevel: this.sessionEffortLevels.get(sessionId),
668
+ status: this.sessionStatus.get(sessionId),
649
669
  }
650
670
  }
651
671
 
@@ -743,7 +763,7 @@ export class SessionManager {
743
763
  sessionId: s.sessionId,
744
764
  summary: s.summary || 'Untitled',
745
765
  lastModified: s.lastModified,
746
- status: this.runningSessionIds.has(s.sessionId) ? 'running' as const : 'idle' as const,
766
+ status: this.sessionStatus.get(s.sessionId),
747
767
  cwd: (s as Record<string, unknown>).cwd as string | undefined,
748
768
  }))
749
769
  }
@@ -814,7 +834,7 @@ export class SessionManager {
814
834
  const session = unstable_v2_resumeSession(sessionId, sessionOptions)
815
835
  try { process.chdir(originalCwd) } catch { /* ignore */ }
816
836
  this.sessions.set(sessionId, session)
817
- this.runningSessionIds.add(sessionId)
837
+ this.sessionStatus.set(sessionId, 'idle')
818
838
 
819
839
  // Broadcast session_resumed to all listeners so other connections update their UI
820
840
  this.broadcast(sessionId, (l) => l.onMessage(sessionId, {
@@ -832,7 +852,7 @@ export class SessionManager {
832
852
  this.closedSessionIds.add(sessionId)
833
853
  session.close()
834
854
  this.sessions.delete(sessionId)
835
- this.runningSessionIds.delete(sessionId)
855
+ this.sessionStatus.set(sessionId, 'stopped')
836
856
  this.streamingSessionIds.delete(sessionId)
837
857
 
838
858
  // Clean up idle timer
@@ -0,0 +1,30 @@
1
+ export type SessionStatus = 'idle' | 'running' | 'stopped'
2
+
3
+ export class SessionStatusTracker {
4
+ private map = new Map<string, SessionStatus>()
5
+ private onChange?: (sessionId: string, status: SessionStatus) => void
6
+
7
+ constructor(onChange?: (sessionId: string, status: SessionStatus) => void) {
8
+ this.onChange = onChange
9
+ }
10
+
11
+ get(sessionId: string): SessionStatus {
12
+ return this.map.get(sessionId) ?? 'stopped'
13
+ }
14
+
15
+ set(sessionId: string, status: SessionStatus): void {
16
+ const prev = this.map.get(sessionId)
17
+ this.map.set(sessionId, status)
18
+ if (prev !== status && this.onChange) {
19
+ this.onChange(sessionId, status)
20
+ }
21
+ }
22
+
23
+ delete(sessionId: string): void {
24
+ this.map.delete(sessionId)
25
+ }
26
+
27
+ getAll(): Map<string, SessionStatus> {
28
+ return new Map(this.map)
29
+ }
30
+ }
@@ -158,7 +158,7 @@ export interface SessionInfo {
158
158
  sessionId: string
159
159
  summary: string
160
160
  lastModified: number
161
- status: 'idle' | 'running'
161
+ status: 'idle' | 'running' | 'stopped'
162
162
  cwd?: string
163
163
  }
164
164
 
@@ -277,6 +277,7 @@ export interface SessionStateMessage {
277
277
  sessionId: string
278
278
  model?: string
279
279
  effortLevel?: EffortLevel
280
+ status?: 'idle' | 'running' | 'stopped'
280
281
  }
281
282
 
282
283
  export interface SubagentMessagesMessage {
@@ -302,6 +303,12 @@ export interface SessionSettingsMessage {
302
303
  settings: Record<string, unknown>
303
304
  }
304
305
 
306
+ export interface SessionStatusMessage {
307
+ type: 'session_status'
308
+ sessionId: string
309
+ status: string
310
+ }
311
+
305
312
  export type ServerMessage =
306
313
  | SessionCreatedMessage
307
314
  | SessionListMessage
@@ -325,3 +332,4 @@ export type ServerMessage =
325
332
  | SubagentMessagesMessage
326
333
  | ElicitationRequestMessage
327
334
  | SessionSettingsMessage
335
+ | SessionStatusMessage
@@ -135,6 +135,12 @@ export function createWsHandler(sessionManager: SessionManager, log: FastifyBase
135
135
  return // don't forward synthetic message
136
136
  }
137
137
 
138
+ // Forward session_status as top-level message (not wrapped in sdk_message)
139
+ if (msg.type === 'session_status') {
140
+ send({ type: 'session_status', sessionId: msg.sessionId as string, status: msg.status as string })
141
+ return
142
+ }
143
+
138
144
  send({ type: 'sdk_message', sessionId: sid, message })
139
145
  },
140
146
  onPermissionRequest(sid, toolUseId, toolName, input, meta) {
@@ -1 +0,0 @@
1
- import{aq as o,ar as n}from"./mermaid.core-B3-wKFnz.js";const t=(r,a)=>o.lang.round(n.parse(r)[a]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-4TB4RGXK-Cpwuap5g.js";import{_ as i}from"./mermaid.core-B3-wKFnz.js";import"./chunk-FMBD7UC4-aLTS4ppS.js";import"./chunk-YZCP3GAM-BCHcb4hM.js";import"./chunk-55IACEB6-MDkMv1Vj.js";import"./chunk-EDXVE4YY-fWFZkL3K.js";import"./index-BlOeGePC.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-4TB4RGXK-Cpwuap5g.js";import{_ as i}from"./mermaid.core-B3-wKFnz.js";import"./chunk-FMBD7UC4-aLTS4ppS.js";import"./chunk-YZCP3GAM-BCHcb4hM.js";import"./chunk-55IACEB6-MDkMv1Vj.js";import"./chunk-EDXVE4YY-fWFZkL3K.js";import"./index-BlOeGePC.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{b as r}from"./graph-CwsRpftd.js";var e=4;function a(o){return r(o,e)}export{a as c};
@@ -1 +0,0 @@
1
- import{s as e,b as r,a,S as s}from"./chunk-OYMX7WX6-CdiOjMj9.js";import{_ as i}from"./mermaid.core-B3-wKFnz.js";import"./chunk-55IACEB6-MDkMv1Vj.js";import"./chunk-EDXVE4YY-fWFZkL3K.js";import"./index-BlOeGePC.js";var p={parser:a,get db(){return new s(2)},renderer:r,styles:e,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{p as diagram};