claudia-orchestrator 0.1.5 → 0.1.7

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 (167) hide show
  1. package/README.md +22 -77
  2. package/dist/cli-parser.d.ts +0 -2
  3. package/dist/cli-parser.d.ts.map +1 -1
  4. package/dist/cli-parser.js +1 -13
  5. package/dist/cli-parser.js.map +1 -1
  6. package/dist/cui-server.d.ts +1 -3
  7. package/dist/cui-server.d.ts.map +1 -1
  8. package/dist/cui-server.js +175 -63
  9. package/dist/cui-server.js.map +1 -1
  10. package/dist/process-daemon/index.js +2 -2
  11. package/dist/process-daemon/index.js.map +1 -1
  12. package/dist/process-daemon/process-daemon.d.ts.map +1 -1
  13. package/dist/process-daemon/process-daemon.js +27 -9
  14. package/dist/process-daemon/process-daemon.js.map +1 -1
  15. package/dist/process-daemon/process-manager-client.d.ts +14 -3
  16. package/dist/process-daemon/process-manager-client.d.ts.map +1 -1
  17. package/dist/process-daemon/process-manager-client.js +6 -4
  18. package/dist/process-daemon/process-manager-client.js.map +1 -1
  19. package/dist/process-daemon/process-manager-interface.d.ts +16 -2
  20. package/dist/process-daemon/process-manager-interface.d.ts.map +1 -1
  21. package/dist/process-daemon/test-daemon.js +24 -24
  22. package/dist/process-daemon/test-daemon.js.map +1 -1
  23. package/dist/process-daemon/types.d.ts +13 -1
  24. package/dist/process-daemon/types.d.ts.map +1 -1
  25. package/dist/process-daemon/types.js.map +1 -1
  26. package/dist/routes/analysis.routes.d.ts +13 -0
  27. package/dist/routes/analysis.routes.d.ts.map +1 -0
  28. package/dist/routes/analysis.routes.js +103 -0
  29. package/dist/routes/analysis.routes.js.map +1 -0
  30. package/dist/routes/conversation.routes.d.ts +1 -1
  31. package/dist/routes/conversation.routes.d.ts.map +1 -1
  32. package/dist/routes/conversation.routes.js +76 -27
  33. package/dist/routes/conversation.routes.js.map +1 -1
  34. package/dist/routes/insights.routes.d.ts.map +1 -1
  35. package/dist/routes/insights.routes.js +371 -29
  36. package/dist/routes/insights.routes.js.map +1 -1
  37. package/dist/routes/license.routes.d.ts.map +1 -1
  38. package/dist/routes/license.routes.js +0 -16
  39. package/dist/routes/license.routes.js.map +1 -1
  40. package/dist/routes/log.routes.d.ts.map +1 -1
  41. package/dist/routes/log.routes.js +52 -0
  42. package/dist/routes/log.routes.js.map +1 -1
  43. package/dist/routes/pending-question.routes.d.ts +9 -0
  44. package/dist/routes/pending-question.routes.d.ts.map +1 -0
  45. package/dist/routes/pending-question.routes.js +154 -0
  46. package/dist/routes/pending-question.routes.js.map +1 -0
  47. package/dist/routes/process-events.routes.d.ts +8 -0
  48. package/dist/routes/process-events.routes.d.ts.map +1 -0
  49. package/dist/routes/process-events.routes.js +141 -0
  50. package/dist/routes/process-events.routes.js.map +1 -0
  51. package/dist/routes/question.routes.d.ts +1 -1
  52. package/dist/routes/question.routes.d.ts.map +1 -1
  53. package/dist/routes/question.routes.js +10 -9
  54. package/dist/routes/question.routes.js.map +1 -1
  55. package/dist/routes/streaming.routes.d.ts.map +1 -1
  56. package/dist/routes/streaming.routes.js +3 -7
  57. package/dist/routes/streaming.routes.js.map +1 -1
  58. package/dist/services/anthropic-service.d.ts +44 -6
  59. package/dist/services/anthropic-service.d.ts.map +1 -1
  60. package/dist/services/anthropic-service.js +278 -92
  61. package/dist/services/anthropic-service.js.map +1 -1
  62. package/dist/services/claude-history-reader.d.ts +11 -0
  63. package/dist/services/claude-history-reader.d.ts.map +1 -1
  64. package/dist/services/claude-history-reader.js +105 -6
  65. package/dist/services/claude-history-reader.js.map +1 -1
  66. package/dist/services/claude-process-manager.d.ts +24 -4
  67. package/dist/services/claude-process-manager.d.ts.map +1 -1
  68. package/dist/services/claude-process-manager.js +137 -9
  69. package/dist/services/claude-process-manager.js.map +1 -1
  70. package/dist/services/claudia-service.d.ts.map +1 -1
  71. package/dist/services/claudia-service.js.map +1 -1
  72. package/dist/services/config-service.d.ts.map +1 -1
  73. package/dist/services/config-service.js +3 -18
  74. package/dist/services/config-service.js.map +1 -1
  75. package/dist/services/conversation-status-manager.d.ts +12 -0
  76. package/dist/services/conversation-status-manager.d.ts.map +1 -1
  77. package/dist/services/conversation-status-manager.js +38 -4
  78. package/dist/services/conversation-status-manager.js.map +1 -1
  79. package/dist/services/cost-tracker.d.ts +27 -2
  80. package/dist/services/cost-tracker.d.ts.map +1 -1
  81. package/dist/services/cost-tracker.js +101 -11
  82. package/dist/services/cost-tracker.js.map +1 -1
  83. package/dist/services/debug-log.d.ts +6 -0
  84. package/dist/services/debug-log.d.ts.map +1 -0
  85. package/dist/services/debug-log.js +27 -0
  86. package/dist/services/debug-log.js.map +1 -0
  87. package/dist/services/gemini-service.d.ts +0 -22
  88. package/dist/services/gemini-service.d.ts.map +1 -1
  89. package/dist/services/gemini-service.js +8 -137
  90. package/dist/services/gemini-service.js.map +1 -1
  91. package/dist/services/insight-queue.js +3 -3
  92. package/dist/services/insight-queue.js.map +1 -1
  93. package/dist/services/insights-event-log.d.ts +196 -0
  94. package/dist/services/insights-event-log.d.ts.map +1 -0
  95. package/dist/services/insights-event-log.js +319 -0
  96. package/dist/services/insights-event-log.js.map +1 -0
  97. package/dist/services/insights-service.d.ts +49 -0
  98. package/dist/services/insights-service.d.ts.map +1 -1
  99. package/dist/services/insights-service.js +655 -100
  100. package/dist/services/insights-service.js.map +1 -1
  101. package/dist/services/license-service.d.ts.map +1 -1
  102. package/dist/services/license-service.js.map +1 -1
  103. package/dist/services/pending-question-service.d.ts +97 -0
  104. package/dist/services/pending-question-service.d.ts.map +1 -0
  105. package/dist/services/pending-question-service.js +223 -0
  106. package/dist/services/pending-question-service.js.map +1 -0
  107. package/dist/services/process-event-log.d.ts +263 -0
  108. package/dist/services/process-event-log.d.ts.map +1 -0
  109. package/dist/services/process-event-log.js +509 -0
  110. package/dist/services/process-event-log.js.map +1 -0
  111. package/dist/services/process-manager-factory.d.ts +19 -1
  112. package/dist/services/process-manager-factory.d.ts.map +1 -1
  113. package/dist/services/process-manager-factory.js +88 -7
  114. package/dist/services/process-manager-factory.js.map +1 -1
  115. package/dist/services/question-tracker.d.ts +4 -0
  116. package/dist/services/question-tracker.d.ts.map +1 -1
  117. package/dist/services/question-tracker.js +6 -0
  118. package/dist/services/question-tracker.js.map +1 -1
  119. package/dist/services/session-activity-watcher.d.ts +1 -1
  120. package/dist/services/session-activity-watcher.d.ts.map +1 -1
  121. package/dist/services/session-activity-watcher.js +10 -7
  122. package/dist/services/session-activity-watcher.js.map +1 -1
  123. package/dist/services/session-analysis-service.d.ts +72 -0
  124. package/dist/services/session-analysis-service.d.ts.map +1 -0
  125. package/dist/services/session-analysis-service.js +373 -0
  126. package/dist/services/session-analysis-service.js.map +1 -0
  127. package/dist/services/session-info-service.d.ts +23 -0
  128. package/dist/services/session-info-service.d.ts.map +1 -1
  129. package/dist/services/session-info-service.js +46 -10
  130. package/dist/services/session-info-service.js.map +1 -1
  131. package/dist/services/session-insights-service.d.ts.map +1 -1
  132. package/dist/services/session-insights-service.js +96 -19
  133. package/dist/services/session-insights-service.js.map +1 -1
  134. package/dist/services/stream-manager.d.ts.map +1 -1
  135. package/dist/services/stream-manager.js +9 -2
  136. package/dist/services/stream-manager.js.map +1 -1
  137. package/dist/types/config.d.ts +1 -6
  138. package/dist/types/config.d.ts.map +1 -1
  139. package/dist/types/config.js.map +1 -1
  140. package/dist/types/index.d.ts +12 -19
  141. package/dist/types/index.d.ts.map +1 -1
  142. package/dist/types/index.js.map +1 -1
  143. package/dist/types/insights.d.ts +44 -2
  144. package/dist/types/insights.d.ts.map +1 -1
  145. package/dist/utils/server-startup.d.ts +1 -3
  146. package/dist/utils/server-startup.d.ts.map +1 -1
  147. package/dist/utils/server-startup.js +3 -14
  148. package/dist/utils/server-startup.js.map +1 -1
  149. package/dist/web/assets/main-CFG6c-xC.css +1 -0
  150. package/dist/web/assets/main-DoNPc_qs.js +484 -0
  151. package/dist/web/index.html +2 -2
  152. package/package.json +14 -5
  153. package/scripts/postinstall.js +27 -3
  154. package/dist/middleware/auth.d.ts +0 -18
  155. package/dist/middleware/auth.d.ts.map +0 -1
  156. package/dist/middleware/auth.js +0 -136
  157. package/dist/middleware/auth.js.map +0 -1
  158. package/dist/routes/gemini.routes.d.ts +0 -4
  159. package/dist/routes/gemini.routes.d.ts.map +0 -1
  160. package/dist/routes/gemini.routes.js +0 -93
  161. package/dist/routes/gemini.routes.js.map +0 -1
  162. package/dist/routes/notifications.routes.d.ts +0 -4
  163. package/dist/routes/notifications.routes.d.ts.map +0 -1
  164. package/dist/routes/notifications.routes.js +0 -71
  165. package/dist/routes/notifications.routes.js.map +0 -1
  166. package/dist/web/assets/main-CgZfAwiY.js +0 -269
  167. package/dist/web/assets/main-wtQycIPx.css +0 -1
package/README.md CHANGED
@@ -1,107 +1,52 @@
1
- # Claudia Orchestrator
1
+ # Claudia
2
2
 
3
- A cognitive telemetry dashboard for Claude Code sessions. Orchestrate multiple AI coding sessions with real-time streaming, AI-powered insights, and session analysis.
3
+ A dashboard for orchestrating Claude Code sessions. Run multiple AI coding sessions in parallel with real-time streaming and AI-powered insights.
4
4
 
5
- ## What This Is
6
-
7
- Claudia Orchestrator wraps Claude Code CLI sessions and adds:
8
-
9
- - **Live insight surfacing** — See what the model is doing: assumptions, uncertainties, scope drift, progress
10
- - **Multi-session management** — Run multiple Claude Code sessions in parallel from one dashboard
11
- - **Real-time streaming** — PTY-based process management with instant output
12
- - **Session persistence** — Sessions survive server restarts
13
-
14
- ## Quick Start
15
-
16
- **Requirements:** Node.js 22+, Claude Code CLI installed and authenticated
5
+ ## Install
17
6
 
18
7
  ```bash
19
- # Run directly (no install needed)
20
8
  npx claudia-orchestrator
9
+ ```
21
10
 
22
- # Or install globally
11
+ Or install globally:
12
+
13
+ ```bash
23
14
  npm install -g claudia-orchestrator
24
15
  claudia
25
16
  ```
26
17
 
27
- The dashboard runs at `http://localhost:3001`. Your auth token is displayed on first launch — open the URL shown in terminal.
18
+ **Requirements:** Node.js 22+, Claude Code CLI installed and authenticated
28
19
 
29
20
  ## Features
30
21
 
31
- ### Session Orchestration
32
- - Start new Claude Code sessions from the browser
33
- - Resume previous sessions
34
- - Run multiple sessions in parallel
35
- - Stream output in real-time
36
-
37
- ### AI Insights (Beta)
38
- During beta, all users get access to AI-powered session insights:
39
- - **Current State** — What's happening right now
40
- - **Milestones** — Progress tracking with done/current/pending tasks
41
- - **Notable Events** — Errors, breakthroughs, decisions, course corrections
42
- - **Context Panels** — Key information extracted from the session
43
-
44
- Insights require your own Anthropic API key (set `ANTHROPIC_API_KEY` environment variable).
45
-
46
- ### Voice Input
47
- Dictation powered by Gemini 2.5 Flash. Requires a Google API key (`GOOGLE_API_KEY`).
48
-
49
- ### Push Notifications
50
- Get notified when sessions complete or need input. Supports web-push and ntfy.sh.
22
+ - **Multi-session management** — Run multiple Claude Code sessions in parallel
23
+ - **Real-time streaming** PTY-based output with instant feedback
24
+ - **AI insights** — See what the model is doing: assumptions, progress, uncertainties
25
+ - **Session persistence** Sessions survive server restarts
26
+ - **Voice input** Dictation powered by Gemini 2.5 Flash
27
+ - **Push notifications** — Get notified when sessions complete or need input
51
28
 
52
- ## Configuration
29
+ ## Setup
53
30
 
54
- Config lives in `~/.claudia/`:
55
- - `config.json` — Server settings, API keys, auth token
56
- - `session-info.db` — Session metadata and insights cache
31
+ The dashboard runs at `http://localhost:3001`. Your auth token is displayed on first launch.
57
32
 
58
- ### Environment Variables
33
+ ### API Keys
59
34
 
60
35
  | Variable | Purpose |
61
36
  |----------|---------|
62
37
  | `ANTHROPIC_API_KEY` | Required for AI insights |
63
38
  | `GOOGLE_API_KEY` | Optional for voice dictation |
64
39
 
40
+ Set these in your environment or in `~/.claudia/config.json`.
41
+
65
42
  ### Remote Access
66
43
 
67
44
  1. Edit `~/.claudia/config.json`, set `server.host` to `0.0.0.0`
68
- 2. Use HTTPS via reverse proxy (Caddy, nginx) for security
69
- 3. Or use Tailscale Serve for secure access
70
-
71
- ## System Requirements
72
-
73
- - **Node.js:** 22+ (native modules: node-pty, better-sqlite3, sharp)
74
- - **Claude Code:** Must be installed and authenticated (`claude` command available)
75
- - **Platform:** Linux, macOS, WSL2
76
-
77
- ## Beta Status
78
-
79
- This is beta software. During beta:
80
- - All Pro features are enabled for everyone
81
- - AI insights require your own API key
82
- - Session limits are not enforced
83
-
84
- ## Troubleshooting
85
-
86
- ### "Module not found" or native module errors
87
- ```bash
88
- # Rebuild native modules for your Node version
89
- rm -rf node_modules
90
- npm install
91
- ```
92
-
93
- ### Sessions not starting
94
- Ensure Claude Code CLI is working:
95
- ```bash
96
- claude --version
97
- ```
98
-
99
- ### Insights not generating
100
- Check that `ANTHROPIC_API_KEY` is set in `~/.claudia/config.json` or as an environment variable.
45
+ 2. Use HTTPS via reverse proxy (Caddy, nginx) or Tailscale Serve
101
46
 
102
- ## Credits
47
+ ## Status
103
48
 
104
- Forked from [cui](https://github.com/BMPixel/cui) by Wenbo Pan. Extended with cognitive telemetry and licensing infrastructure.
49
+ Claudia is in beta. For feedback or support, email jason@liggi.dev.
105
50
 
106
51
  ## License
107
52
 
@@ -1,8 +1,6 @@
1
1
  export interface CLIConfig {
2
2
  port?: number;
3
3
  host?: string;
4
- token?: string;
5
- skipAuthToken?: boolean;
6
4
  }
7
5
  /**
8
6
  * Parse command line arguments
@@ -1 +1 @@
1
- {"version":3,"file":"cli-parser.d.ts","sourceRoot":"","sources":["../src/cli-parser.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAS,CAsDnD"}
1
+ {"version":3,"file":"cli-parser.d.ts","sourceRoot":"","sources":["../src/cli-parser.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAS,CAyCnD"}
@@ -34,21 +34,9 @@ export function parseArgs(argv) {
34
34
  process.exit(1);
35
35
  }
36
36
  break;
37
- case '--token':
38
- if (i + 1 < args.length) {
39
- config.token = args[++i];
40
- }
41
- else {
42
- logger.error('--token requires a value');
43
- process.exit(1);
44
- }
45
- break;
46
- case '--skip-auth-token':
47
- config.skipAuthToken = true;
48
- break;
49
37
  default:
50
38
  logger.error(`Unknown argument: ${arg}`);
51
- logger.info('Usage: claudia [--port <number>] [--host <string>] [--token <string>] [--skip-auth-token]');
39
+ logger.info('Usage: claudia [--port <number>] [--host <string>]');
52
40
  process.exit(1);
53
41
  }
54
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"cli-parser.js","sourceRoot":"","sources":["../src/cli-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AASpD;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAc,EAAE,CAAC;IAE7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAEpB,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,QAAQ;gBACX,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;oBACxB,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC1C,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,IAAI,KAAK,EAAE,CAAC;wBAC7D,MAAM,CAAC,IAAI,GAAG,SAAS,CAAC;oBAC1B,CAAC;yBAAM,CAAC;wBACN,MAAM,CAAC,KAAK,CAAC,uBAAuB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;wBAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;oBAClB,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;oBACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAClB,CAAC;gBACD,MAAM;YAER,KAAK,QAAQ;gBACX,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;oBACxB,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC1B,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;oBACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAClB,CAAC;gBACD,MAAM;YAER,KAAK,SAAS;gBACZ,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;oBACxB,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC3B,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;oBACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAClB,CAAC;gBACD,MAAM;YAER,KAAK,mBAAmB;gBACtB,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC;gBAC5B,MAAM;YAER;gBACE,MAAM,CAAC,KAAK,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;gBACzC,MAAM,CAAC,IAAI,CAAC,2FAA2F,CAAC,CAAC;gBACzG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"cli-parser.js","sourceRoot":"","sources":["../src/cli-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAOpD;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAc,EAAE,CAAC;IAE7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAEpB,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,QAAQ;gBACX,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;oBACxB,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC1C,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,IAAI,KAAK,EAAE,CAAC;wBAC7D,MAAM,CAAC,IAAI,GAAG,SAAS,CAAC;oBAC1B,CAAC;yBAAM,CAAC;wBACN,MAAM,CAAC,KAAK,CAAC,uBAAuB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;wBAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;oBAClB,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;oBACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAClB,CAAC;gBACD,MAAM;YAER,KAAK,QAAQ;gBACX,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;oBACxB,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC1B,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;oBACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAClB,CAAC;gBACD,MAAM;YAER;gBACE,MAAM,CAAC,KAAK,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;gBACzC,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;gBAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -8,9 +8,9 @@ export declare class CUIServer {
8
8
  private processManager;
9
9
  private streamManager;
10
10
  private historyReader;
11
- private statusTracker;
12
11
  private permissionTracker;
13
12
  private questionTracker;
13
+ private pendingQuestionService;
14
14
  private mcpConfigGenerator;
15
15
  private fileSystemService;
16
16
  private configService;
@@ -28,8 +28,6 @@ export declare class CUIServer {
28
28
  constructor(configOverrides?: {
29
29
  port?: number;
30
30
  host?: string;
31
- token?: string;
32
- skipAuthToken?: boolean;
33
31
  });
34
32
  /**
35
33
  * Get the Express app instance
@@ -1 +1 @@
1
- {"version":3,"file":"cui-server.d.ts","sourceRoot":"","sources":["../src/cui-server.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AA2D3C;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,GAAG,CAAU;IACrB,OAAO,CAAC,MAAM,CAAC,CAAwB;IACvC,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,yBAAyB,CAA4B;IAC7D,OAAO,CAAC,yBAAyB,CAA4B;IAC7D,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,aAAa,CAAC,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,eAAe,CAAC,CAA4E;gBAExF,eAAe,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE;IA4DvG;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACH,OAAO,IAAI,MAAM;IAIjB;;OAEG;IACH,OAAO,IAAI,MAAM;IAIjB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAsHjC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA4G5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwE3B;;OAEG;YACW,OAAO;IA6BrB,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,WAAW;IAuEnB,OAAO,CAAC,8BAA8B;IAiItC,OAAO,CAAC,iCAAiC;IA6BzC,OAAO,CAAC,+BAA+B;YA4BzB,wBAAwB;CAgCvC"}
1
+ {"version":3,"file":"cui-server.d.ts","sourceRoot":"","sources":["../src/cui-server.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AA0D3C;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,GAAG,CAAU;IACrB,OAAO,CAAC,MAAM,CAAC,CAAwB;IACvC,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,sBAAsB,CAAyB;IACvD,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,yBAAyB,CAA4B;IAC7D,OAAO,CAAC,yBAAyB,CAA4B;IAC7D,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,aAAa,CAAC,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,eAAe,CAAC,CAAmC;gBAE/C,eAAe,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;IAwD9D;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACH,OAAO,IAAI,MAAM;IAIjB;;OAEG;IACH,OAAO,IAAI,MAAM;IAIjB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAoIjC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsG5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwE3B;;OAEG;YACW,OAAO;IA6BrB,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,WAAW;IAgEnB,OAAO,CAAC,8BAA8B;IAsQtC,OAAO,CAAC,iCAAiC;IA6BzC,OAAO,CAAC,+BAA+B;YA8BzB,wBAAwB;CAgCvC"}
@@ -11,6 +11,7 @@ import { StreamManager } from './services/stream-manager.js';
11
11
  import { ClaudeHistoryReader } from './services/claude-history-reader.js';
12
12
  import { PermissionTracker } from './services/permission-tracker.js';
13
13
  import { QuestionTracker } from './services/question-tracker.js';
14
+ import { PendingQuestionService } from './services/pending-question-service.js';
14
15
  import { MCPConfigGenerator } from './services/mcp-config-generator.js';
15
16
  import { FileSystemService } from './services/file-system-service.js';
16
17
  import { ConfigService } from './services/config-service.js';
@@ -22,7 +23,6 @@ import { NotificationService } from './services/notification-service.js';
22
23
  import { WebPushService } from './services/web-push-service.js';
23
24
  import { geminiService } from './services/gemini-service.js';
24
25
  import { anthropicService } from './services/anthropic-service.js';
25
- import { SessionInsightsService } from './services/session-insights-service.js';
26
26
  import { ClaudeRouterService } from './services/claude-router-service.js';
27
27
  import { CUIError } from './types/index.js';
28
28
  import { createLogger } from './services/logger.js';
@@ -30,22 +30,22 @@ import { createConversationRoutes } from './routes/conversation.routes.js';
30
30
  import { createSystemRoutes } from './routes/system.routes.js';
31
31
  import { createPermissionRoutes } from './routes/permission.routes.js';
32
32
  import { createQuestionRoutes } from './routes/question.routes.js';
33
+ import { createPendingQuestionRoutes } from './routes/pending-question.routes.js';
33
34
  import { createFileSystemRoutes } from './routes/filesystem.routes.js';
34
35
  import { createLogRoutes } from './routes/log.routes.js';
35
36
  import { createStreamingRoutes } from './routes/streaming.routes.js';
36
37
  import { createWorkingDirectoriesRoutes } from './routes/working-directories.routes.js';
37
38
  import { createConfigRoutes } from './routes/config.routes.js';
38
- import { createGeminiRoutes } from './routes/gemini.routes.js';
39
- import { createNotificationsRoutes } from './routes/notifications.routes.js';
40
39
  import { createInsightsRoutes } from './routes/insights.routes.js';
41
40
  import { createClaudiaRoutes } from './routes/claudia.routes.js';
42
41
  import { ClaudiaService } from './services/claudia-service.js';
43
42
  import { createLicenseRoutes } from './routes/license.routes.js';
43
+ import { createAnalysisRoutes } from './routes/analysis.routes.js';
44
+ import processEventsRouter from './routes/process-events.routes.js';
44
45
  import { errorHandler } from './middleware/error-handler.js';
45
46
  import { requestLogger } from './middleware/request-logger.js';
46
47
  import { createCorsMiddleware } from './middleware/cors-setup.js';
47
48
  import { queryParser } from './middleware/query-parser.js';
48
- import { authMiddleware, createAuthMiddleware } from './middleware/auth.js';
49
49
  // ViteExpress will be imported dynamically in initialize() if needed
50
50
  let ViteExpress;
51
51
  /**
@@ -57,9 +57,9 @@ export class CUIServer {
57
57
  processManager; // Initialized in initialize()
58
58
  streamManager;
59
59
  historyReader;
60
- statusTracker;
61
60
  permissionTracker;
62
61
  questionTracker;
62
+ pendingQuestionService;
63
63
  mcpConfigGenerator;
64
64
  fileSystemService;
65
65
  configService;
@@ -94,15 +94,14 @@ export class CUIServer {
94
94
  // Use singleton to ensure event handlers get the same initialized instance
95
95
  this.sessionInfoService = SessionInfoService.getInstance();
96
96
  this.historyReader = new ClaudeHistoryReader(this.sessionInfoService);
97
- // Create a single instance of ConversationStatusManager for both statusTracker and conversationStatusManager
98
97
  this.conversationStatusManager = new ConversationStatusManager();
99
- this.statusTracker = this.conversationStatusManager; // Use the same instance for backward compatibility
100
98
  this.toolMetricsService = new ToolMetricsService();
101
99
  this.fileSystemService = new FileSystemService();
102
100
  // processManager is initialized in initialize() to support daemon mode
103
101
  this.streamManager = new StreamManager();
104
102
  this.permissionTracker = new PermissionTracker();
105
103
  this.questionTracker = new QuestionTracker();
104
+ this.pendingQuestionService = PendingQuestionService.getInstance();
106
105
  this.mcpConfigGenerator = new MCPConfigGenerator(this.fileSystemService);
107
106
  this.workingDirectoriesService = new WorkingDirectoriesService(this.historyReader, this.logger);
108
107
  this.notificationService = new NotificationService();
@@ -111,14 +110,11 @@ export class CUIServer {
111
110
  this.permissionTracker.setNotificationService(this.notificationService);
112
111
  this.permissionTracker.setConversationStatusManager(this.conversationStatusManager);
113
112
  this.permissionTracker.setHistoryReader(this.historyReader);
114
- // Set up background insights computation when sessions end
115
- const insightsService = new SessionInsightsService(this.historyReader, this.sessionInfoService);
116
- this.conversationStatusManager.on('session-ended', ({ claudeSessionId }) => {
117
- // Compute insights in background (fire and forget)
118
- insightsService.computeInsights(claudeSessionId)
119
- .then(insights => insightsService.cacheInsights(claudeSessionId, insights))
120
- .catch(err => this.logger.debug('Background insights computation failed', { claudeSessionId, error: err }));
121
- });
113
+ // NOTE: Session-ended insights computation was removed because it raced with
114
+ // the event-driven InsightsService. The session-ended handler would read the
115
+ // session file before all messages were flushed, caching insights with
116
+ // messageCount=1. The event-driven system (InsightsService) now handles all
117
+ // insight generation via file watching and action counting.
122
118
  this.logger.debug('Services initialized (processManager deferred to initialize())');
123
119
  this.setupMiddleware();
124
120
  // Routes and processManager integration are set up in initialize()
@@ -155,15 +151,24 @@ export class CUIServer {
155
151
  this.logger.debug('Initializing session info service');
156
152
  await this.sessionInfoService.initialize();
157
153
  this.logger.debug('Session info service initialized successfully');
158
- // Initialize process manager
159
- this.logger.debug('Initializing process manager');
160
- this.processManager = await createProcessManager({
161
- historyReader: this.historyReader,
162
- statusTracker: this.statusTracker,
163
- toolMetricsService: this.toolMetricsService,
164
- sessionInfoService: this.sessionInfoService,
165
- fileSystemService: this.fileSystemService
166
- });
154
+ // Initialize pending question service (uses same DB as session info)
155
+ this.logger.debug('Initializing pending question service');
156
+ await this.pendingQuestionService.initialize();
157
+ this.logger.debug('Pending question service initialized successfully');
158
+ // Initialize process manager (skip if already set, e.g. by tests)
159
+ if (!this.processManager) {
160
+ this.logger.debug('Initializing process manager');
161
+ this.processManager = await createProcessManager({
162
+ historyReader: this.historyReader,
163
+ statusTracker: this.conversationStatusManager,
164
+ toolMetricsService: this.toolMetricsService,
165
+ sessionInfoService: this.sessionInfoService,
166
+ fileSystemService: this.fileSystemService
167
+ });
168
+ }
169
+ else {
170
+ this.logger.debug('Process manager already set, skipping initialization');
171
+ }
167
172
  this.processManager.setNotificationService(this.notificationService);
168
173
  this.processManager.setConversationStatusManager(this.conversationStatusManager);
169
174
  // Set up event listeners now that processManager exists
@@ -184,8 +189,12 @@ export class CUIServer {
184
189
  this.logger.debug('Cost tracker initialized successfully');
185
190
  // Initialize insights service (unified event-driven system)
186
191
  this.logger.debug('Initializing insights service');
187
- const { initializeInsightsService } = await import('./services/insights-service.js');
192
+ const { initializeInsightsService, getInsightsService } = await import('./services/insights-service.js');
188
193
  initializeInsightsService();
194
+ // Wire up session-ended to expedite insight patches
195
+ this.conversationStatusManager.on('session-ended', ({ claudeSessionId }) => {
196
+ getInsightsService().handleSessionEnded(claudeSessionId);
197
+ });
189
198
  this.logger.debug('Insights service initialized successfully');
190
199
  // Initialize router service if configured
191
200
  await this.initializeOrReloadRouter(config);
@@ -277,12 +286,9 @@ export class CUIServer {
277
286
  address: this.server?.address()
278
287
  });
279
288
  // Display startup info now that server is actually listening
280
- const config = this.configService.getConfig();
281
289
  displayServerStartup({
282
290
  host: this.host,
283
291
  port: this.port,
284
- authToken: this.configOverrides?.token ?? config.authToken,
285
- skipAuthToken: this.configOverrides?.skipAuthToken,
286
292
  logger: this.logger
287
293
  });
288
294
  // Configure and bind ViteExpress AFTER server is listening
@@ -310,12 +316,9 @@ export class CUIServer {
310
316
  mode: process.env.NODE_ENV || 'production'
311
317
  });
312
318
  // Display startup info now that server is actually listening
313
- const config = this.configService.getConfig();
314
319
  displayServerStartup({
315
320
  host: this.host,
316
321
  port: this.port,
317
- authToken: this.configOverrides?.token ?? config.authToken,
318
- skipAuthToken: this.configOverrides?.skipAuthToken,
319
322
  logger: this.logger
320
323
  });
321
324
  resolve();
@@ -461,38 +464,21 @@ export class CUIServer {
461
464
  // System routes (includes health check) - before auth
462
465
  this.app.use('/api/system', createSystemRoutes(this.processManager, this.historyReader));
463
466
  this.app.use('/', createSystemRoutes(this.processManager, this.historyReader)); // For /health at root
464
- // Permission routes - before auth (needed for MCP server communication)
467
+ // Permission routes (needed for MCP server communication)
465
468
  this.app.use('/api/permissions', createPermissionRoutes(this.permissionTracker));
466
- // Notifications routes - before auth (needed for service worker subscription on first load)
467
- this.app.use('/api/notifications', createNotificationsRoutes(this.webPushService));
468
- // Apply auth middleware to all other API routes unless skipAuthToken is set
469
- if (!this.configOverrides?.skipAuthToken) {
470
- if (this.configOverrides?.token) {
471
- // Use custom auth middleware with token override
472
- this.app.use('/api', createAuthMiddleware(this.configOverrides.token));
473
- this.logger.info('Using custom authentication token from CLI');
474
- }
475
- else {
476
- // Use default auth middleware
477
- this.app.use('/api', authMiddleware);
478
- }
479
- }
480
- else {
481
- this.logger.warn('Authentication middleware is disabled - API endpoints are not protected!');
482
- }
483
469
  // API routes
484
470
  // IMPORTANT: Insights routes must be registered BEFORE conversation routes
485
471
  // because conversation routes have a /:sessionId catch-all pattern that would
486
472
  // otherwise match paths like /activity-check
487
473
  this.app.use('/api/conversations', createInsightsRoutes(this.historyReader, this.sessionInfoService));
488
- this.app.use('/api/conversations', createConversationRoutes(this.processManager, this.historyReader, this.statusTracker, this.sessionInfoService, this.conversationStatusManager, this.toolMetricsService));
474
+ this.app.use('/api/conversations', createConversationRoutes(this.processManager, this.historyReader, this.conversationStatusManager, this.sessionInfoService, this.toolMetricsService));
489
475
  this.app.use('/api/filesystem', createFileSystemRoutes(this.fileSystemService));
490
476
  this.app.use('/api/logs', createLogRoutes());
491
477
  this.app.use('/api/stream', createStreamingRoutes(this.streamManager));
492
478
  this.app.use('/api/working-directories', createWorkingDirectoriesRoutes(this.workingDirectoriesService));
493
479
  this.app.use('/api/config', createConfigRoutes(this.configService));
494
- this.app.use('/api/gemini', createGeminiRoutes(geminiService));
495
480
  this.app.use('/api/questions', createQuestionRoutes(this.questionTracker, this.processManager));
481
+ this.app.use('/api/pending-questions', createPendingQuestionRoutes(this.pendingQuestionService, this.processManager, this.historyReader));
496
482
  // Claudia orchestrator routes
497
483
  const claudiaService = ClaudiaService.getInstance({
498
484
  processManager: this.processManager,
@@ -502,6 +488,10 @@ export class CUIServer {
502
488
  this.app.use('/api/claudia', createClaudiaRoutes(claudiaService));
503
489
  // License routes
504
490
  this.app.use('/api/license', createLicenseRoutes());
491
+ // Post-session analysis routes
492
+ this.app.use('/api/analysis', createAnalysisRoutes());
493
+ // Process event log routes (observability)
494
+ this.app.use('/api/process-events', processEventsRouter);
505
495
  // React Router catch-all - must be after all API routes
506
496
  const isDev = process.env.NODE_ENV === 'development';
507
497
  if (!isDev) {
@@ -518,6 +508,9 @@ export class CUIServer {
518
508
  this.logger.debug('Setting up ProcessManager integration with StreamManager');
519
509
  // Set up tool metrics service to listen to claude messages
520
510
  this.toolMetricsService.listenToClaudeMessages(this.processManager);
511
+ // Local map to track streamingId -> sessionId (populated from system_init messages)
512
+ // This is a simple, reliable source of truth that doesn't depend on external services
513
+ const streamingSessionMap = new Map();
521
514
  // Forward Claude messages to stream
522
515
  this.processManager.on('claude-message', ({ streamingId, message }) => {
523
516
  this.logger.debug('Received claude-message event', {
@@ -528,6 +521,15 @@ export class CUIServer {
528
521
  contentLength: message?.content?.length || 0,
529
522
  messageKeys: message ? Object.keys(message) : []
530
523
  });
524
+ // Capture sessionId from system init messages (this is our source of truth)
525
+ if (message && message.type === 'system' && message.subtype === 'init' && message.session_id) {
526
+ streamingSessionMap.set(streamingId, message.session_id);
527
+ this.logger.info('[SESSION-MAP] Captured sessionId from system_init', {
528
+ streamingId: streamingId.slice(0, 8),
529
+ sessionId: message.session_id.slice(0, 8),
530
+ mapSize: streamingSessionMap.size
531
+ });
532
+ }
531
533
  // Skip broadcasting system init messages as they're now included in API response
532
534
  if (message && message.type === 'system' && message.subtype === 'init') {
533
535
  this.logger.debug('Skipping broadcast of system init message (included in API response)', {
@@ -536,15 +538,83 @@ export class CUIServer {
536
538
  });
537
539
  return;
538
540
  }
539
- // Detect AskUserQuestion tool_use and register with QuestionTracker
541
+ // Detect AskUserQuestion tool_use and register with QuestionTracker + persist to DB
542
+ // Debug: log all assistant messages to see what structure we're getting
543
+ if (message && message.type === 'assistant') {
544
+ this.logger.debug('Assistant message received', {
545
+ streamingId,
546
+ hasMessage: !!message.message,
547
+ hasContent: !!message.message?.content,
548
+ contentIsArray: Array.isArray(message.message?.content),
549
+ contentLength: Array.isArray(message.message?.content) ? message.message.content.length : 0
550
+ });
551
+ }
540
552
  if (message && message.type === 'assistant' && message.message?.content) {
541
553
  const content = message.message.content;
542
554
  if (Array.isArray(content)) {
543
555
  for (const block of content) {
556
+ // Debug: log each block to see tool_use blocks
557
+ if (block.type === 'tool_use') {
558
+ this.logger.info('Tool use block detected', {
559
+ streamingId,
560
+ toolName: block.name,
561
+ blockId: block.id
562
+ });
563
+ }
544
564
  if (block.type === 'tool_use' && block.name === 'AskUserQuestion') {
565
+ // Debug: log session state when AskUserQuestion arrives
566
+ const activeSessions = this.conversationStatusManager.getActiveSessionIds();
567
+ this.logger.info('AskUserQuestion detected - checking session state', {
568
+ incomingStreamingId: streamingId,
569
+ activeSessions,
570
+ activeCount: activeSessions.length
571
+ });
545
572
  const input = block.input;
546
573
  if (input.questions) {
547
- this.questionTracker.addQuestionRequest(block.id, input.questions, streamingId);
574
+ // Add to in-memory tracker (for active session UI)
575
+ const questionRequest = this.questionTracker.addQuestionRequest(block.id, input.questions, streamingId);
576
+ // Persist to DB (for recovery after timeout/disconnect)
577
+ // Use local streamingSessionMap as primary source (populated from system_init)
578
+ // Fall back to other sources if needed
579
+ let claudeSessionId = streamingSessionMap.get(streamingId);
580
+ if (!claudeSessionId) {
581
+ claudeSessionId = this.conversationStatusManager.getSessionId(streamingId);
582
+ if (claudeSessionId) {
583
+ this.logger.debug('Using conversationStatusManager sessionId fallback', { streamingId, claudeSessionId });
584
+ }
585
+ }
586
+ if (!claudeSessionId) {
587
+ claudeSessionId = this.processManager.getClaudeSessionId(streamingId);
588
+ if (claudeSessionId) {
589
+ this.logger.debug('Using processManager sessionId fallback', { streamingId, claudeSessionId });
590
+ }
591
+ }
592
+ if (claudeSessionId) {
593
+ this.pendingQuestionService.addQuestion(questionRequest.id, claudeSessionId, streamingId, block.id, input.questions);
594
+ this.logger.info('AskUserQuestion persisted for recovery', {
595
+ questionId: questionRequest.id,
596
+ sessionId: claudeSessionId,
597
+ streamingId,
598
+ questionCount: input.questions.length
599
+ });
600
+ // Stop the session immediately to prevent Claude from continuing
601
+ // The session will be resumed when the user answers the question
602
+ this.logger.info('Stopping session to await user answer', { streamingId, claudeSessionId });
603
+ this.processManager.stopConversation(streamingId).catch((err) => {
604
+ this.logger.warn('Failed to stop session after AskUserQuestion', {
605
+ streamingId,
606
+ error: err instanceof Error ? err.message : String(err)
607
+ });
608
+ });
609
+ }
610
+ else {
611
+ this.logger.warn('AskUserQuestion NOT persisted - claudeSessionId not found for streamingId', {
612
+ questionId: questionRequest.id,
613
+ streamingId,
614
+ questionCount: input.questions.length,
615
+ activeSessionCount: this.conversationStatusManager.getActiveSessionIds().length
616
+ });
617
+ }
548
618
  }
549
619
  }
550
620
  }
@@ -566,9 +636,23 @@ export class CUIServer {
566
636
  clientCount: this.streamManager.getClientCount(streamingId),
567
637
  wasSuccessful: code === 0
568
638
  });
639
+ // Check for pending questions BEFORE unregistering (we need the sessionId mapping)
640
+ // If there are pending questions, this was likely an AskUserQuestion timeout
641
+ const pendingQuestions = this.pendingQuestionService.getPendingByStreamingId(streamingId);
642
+ if (pendingQuestions.length > 0) {
643
+ this.logger.info('Session closed with pending AskUserQuestion - questions preserved for later resume', {
644
+ streamingId,
645
+ pendingQuestionCount: pendingQuestions.length,
646
+ questionIds: pendingQuestions.map(q => q.id)
647
+ });
648
+ // Questions remain in DB with status='pending' for later retrieval
649
+ // The in-memory tracker will be cleaned up below, but DB persists
650
+ }
651
+ // Clean up local session map
652
+ streamingSessionMap.delete(streamingId);
569
653
  // Unregister session from status tracker
570
654
  this.logger.debug('Unregistering session from status tracker', { streamingId });
571
- this.statusTracker.unregisterActiveSession(streamingId);
655
+ this.conversationStatusManager.unregisterActiveSession(streamingId);
572
656
  // Clean up conversation context (handled automatically in unregisterActiveSession)
573
657
  // Clean up permissions for this streaming session
574
658
  const removedCount = this.permissionTracker.removePermissionsByStreamingId(streamingId);
@@ -578,14 +662,27 @@ export class CUIServer {
578
662
  removedPermissions: removedCount
579
663
  });
580
664
  }
581
- // Clean up questions for this streaming session
582
- const removedQuestions = this.questionTracker.removeQuestionsByStreamingId(streamingId);
583
- if (removedQuestions > 0) {
584
- this.logger.debug('Cleaned up questions for closed session', {
665
+ // Check for in-memory questions BEFORE removing them
666
+ // If there are pending questions, we need to keep them for error suppression
667
+ // They'll be cleaned up when answered or expired, not on session close
668
+ const inMemoryQuestions = this.questionTracker.getQuestionsByStreamingId(streamingId);
669
+ if (inMemoryQuestions.length > 0) {
670
+ this.logger.info('Preserving in-memory questions for error suppression (awaiting user answer)', {
585
671
  streamingId,
586
- removedQuestions
672
+ inMemoryQuestionCount: inMemoryQuestions.length,
673
+ questionIds: inMemoryQuestions.map(q => q.id)
587
674
  });
588
675
  }
676
+ else {
677
+ // No pending questions - safe to clean up
678
+ const removedQuestions = this.questionTracker.removeQuestionsByStreamingId(streamingId);
679
+ if (removedQuestions > 0) {
680
+ this.logger.debug('Cleaned up in-memory questions for closed session', {
681
+ streamingId,
682
+ removedQuestions
683
+ });
684
+ }
685
+ }
589
686
  if (code === 0) {
590
687
  // Session completion notification removed
591
688
  }
@@ -593,15 +690,28 @@ export class CUIServer {
593
690
  });
594
691
  // Handle process errors
595
692
  this.processManager.on('process-error', ({ streamingId, error }) => {
596
- this.logger.debug('Received process-error event, forwarding to StreamManager', {
693
+ this.logger.debug('Received process-error event', {
597
694
  streamingId,
598
695
  error,
599
696
  errorLength: error?.toString().length || 0,
600
697
  clientCount: this.streamManager.getClientCount(streamingId)
601
698
  });
699
+ // Check if there's a pending question for this streaming session
700
+ // If so, suppress the error - the session was intentionally stopped to wait for user answer
701
+ const pendingQuestions = this.questionTracker.getQuestionsByStreamingId(streamingId);
702
+ if (pendingQuestions.length > 0) {
703
+ this.logger.info('Suppressing process error due to pending AskUserQuestion', {
704
+ streamingId,
705
+ pendingQuestionCount: pendingQuestions.length,
706
+ questionIds: pendingQuestions.map(q => q.id)
707
+ });
708
+ // Still unregister the session but don't broadcast the error
709
+ this.conversationStatusManager.unregisterActiveSession(streamingId);
710
+ return;
711
+ }
602
712
  // Unregister session from status tracker on error
603
713
  this.logger.debug('Unregistering session from status tracker due to error', { streamingId });
604
- this.statusTracker.unregisterActiveSession(streamingId);
714
+ this.conversationStatusManager.unregisterActiveSession(streamingId);
605
715
  // Clean up conversation context on error (handled automatically in unregisterActiveSession)
606
716
  const errorEvent = {
607
717
  type: 'error',
@@ -648,11 +758,13 @@ export class CUIServer {
648
758
  this.logger.debug('Setting up QuestionTracker integration');
649
759
  // Forward question events to stream
650
760
  this.questionTracker.on('question_request', (request) => {
651
- this.logger.debug('Question request event received', {
761
+ // Log at info level to diagnose question flow issues
762
+ this.logger.info('Question request event received, broadcasting to stream', {
652
763
  id: request.id,
653
764
  toolUseId: request.toolUseId,
654
765
  streamingId: request.streamingId,
655
- questionCount: request.questions.length
766
+ questionCount: request.questions.length,
767
+ clientCount: this.streamManager.getClientCount(request.streamingId)
656
768
  });
657
769
  // Broadcast to the appropriate streaming session
658
770
  if (request.streamingId && request.streamingId !== 'unknown') {