intent-hub 0.1.0

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 (214) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/.turbo/cache/019f5ae385027cb1-meta.json +1 -0
  3. package/.turbo/cache/019f5ae385027cb1.tar.zst +0 -0
  4. package/.turbo/cache/040af6112a552a64-meta.json +1 -0
  5. package/.turbo/cache/040af6112a552a64.tar.zst +0 -0
  6. package/.turbo/cache/11195eac3ca5c6ce-meta.json +1 -0
  7. package/.turbo/cache/11195eac3ca5c6ce.tar.zst +0 -0
  8. package/.turbo/cache/13d11166efdf11cf-meta.json +1 -0
  9. package/.turbo/cache/13d11166efdf11cf.tar.zst +0 -0
  10. package/.turbo/cache/19af1af3b136706c-meta.json +1 -0
  11. package/.turbo/cache/19af1af3b136706c.tar.zst +0 -0
  12. package/.turbo/cache/1d33efac91c05b50-meta.json +1 -0
  13. package/.turbo/cache/1d33efac91c05b50.tar.zst +0 -0
  14. package/.turbo/cache/200b85a612af2d13-meta.json +1 -0
  15. package/.turbo/cache/200b85a612af2d13.tar.zst +0 -0
  16. package/.turbo/cache/210308c9ea929858-meta.json +1 -0
  17. package/.turbo/cache/210308c9ea929858.tar.zst +0 -0
  18. package/.turbo/cache/38df8e44c617835e-meta.json +1 -0
  19. package/.turbo/cache/38df8e44c617835e.tar.zst +0 -0
  20. package/.turbo/cache/3e449de5ef60a7a0-meta.json +1 -0
  21. package/.turbo/cache/3e449de5ef60a7a0.tar.zst +0 -0
  22. package/.turbo/cache/51ff024a97c2b4f5-meta.json +1 -0
  23. package/.turbo/cache/51ff024a97c2b4f5.tar.zst +0 -0
  24. package/.turbo/cache/54bc756eeebb377a-meta.json +1 -0
  25. package/.turbo/cache/54bc756eeebb377a.tar.zst +0 -0
  26. package/.turbo/cache/5ed6a840acafc873-meta.json +1 -0
  27. package/.turbo/cache/5ed6a840acafc873.tar.zst +0 -0
  28. package/.turbo/cache/6702dc24e5ca3c2e-meta.json +1 -0
  29. package/.turbo/cache/6702dc24e5ca3c2e.tar.zst +0 -0
  30. package/.turbo/cache/725c72cf71ea854f-meta.json +1 -0
  31. package/.turbo/cache/725c72cf71ea854f.tar.zst +0 -0
  32. package/.turbo/cache/7344ca28d348037a-meta.json +1 -0
  33. package/.turbo/cache/7344ca28d348037a.tar.zst +0 -0
  34. package/.turbo/cache/748fb444cdc0b78c-meta.json +1 -0
  35. package/.turbo/cache/748fb444cdc0b78c.tar.zst +0 -0
  36. package/.turbo/cache/789677c36fe7fb98-meta.json +1 -0
  37. package/.turbo/cache/789677c36fe7fb98.tar.zst +0 -0
  38. package/.turbo/cache/89ff6c6f38dd4a18-meta.json +1 -0
  39. package/.turbo/cache/89ff6c6f38dd4a18.tar.zst +0 -0
  40. package/.turbo/cache/8dbc92d00de0c92e-meta.json +1 -0
  41. package/.turbo/cache/8dbc92d00de0c92e.tar.zst +0 -0
  42. package/.turbo/cache/8eb03f40082b9441-meta.json +1 -0
  43. package/.turbo/cache/8eb03f40082b9441.tar.zst +0 -0
  44. package/.turbo/cache/9157134d4b916017-meta.json +1 -0
  45. package/.turbo/cache/9157134d4b916017.tar.zst +0 -0
  46. package/.turbo/cache/94219ffd32b48e93-meta.json +1 -0
  47. package/.turbo/cache/94219ffd32b48e93.tar.zst +0 -0
  48. package/.turbo/cache/95c1d160b4fa84eb-meta.json +1 -0
  49. package/.turbo/cache/95c1d160b4fa84eb.tar.zst +0 -0
  50. package/.turbo/cache/998833ea02dfb225-meta.json +1 -0
  51. package/.turbo/cache/998833ea02dfb225.tar.zst +0 -0
  52. package/.turbo/cache/a5974ef6ade3eb90-meta.json +1 -0
  53. package/.turbo/cache/a5974ef6ade3eb90.tar.zst +0 -0
  54. package/.turbo/cache/aab811809257decb-meta.json +1 -0
  55. package/.turbo/cache/aab811809257decb.tar.zst +0 -0
  56. package/.turbo/cache/ab2f82a54da854fd-meta.json +1 -0
  57. package/.turbo/cache/ab2f82a54da854fd.tar.zst +0 -0
  58. package/.turbo/cache/abbf4d95d62a7303-meta.json +1 -0
  59. package/.turbo/cache/abbf4d95d62a7303.tar.zst +0 -0
  60. package/.turbo/cache/af4441f519f9ce50-meta.json +1 -0
  61. package/.turbo/cache/af4441f519f9ce50.tar.zst +0 -0
  62. package/.turbo/cache/b9b85aaaf03d00a6-meta.json +1 -0
  63. package/.turbo/cache/b9b85aaaf03d00a6.tar.zst +0 -0
  64. package/.turbo/cache/cd58ee8721bbfed7-meta.json +1 -0
  65. package/.turbo/cache/cd58ee8721bbfed7.tar.zst +0 -0
  66. package/.turbo/cache/d285e48b8afa30b5-meta.json +1 -0
  67. package/.turbo/cache/d285e48b8afa30b5.tar.zst +0 -0
  68. package/.turbo/cache/d33e90229142acce-meta.json +1 -0
  69. package/.turbo/cache/d33e90229142acce.tar.zst +0 -0
  70. package/.turbo/cache/d57839a0d3b04540-meta.json +1 -0
  71. package/.turbo/cache/d57839a0d3b04540.tar.zst +0 -0
  72. package/.turbo/cache/d8554ef2c8b6e5eb-meta.json +1 -0
  73. package/.turbo/cache/d8554ef2c8b6e5eb.tar.zst +0 -0
  74. package/.turbo/cache/dc7375b51290e102-meta.json +1 -0
  75. package/.turbo/cache/dc7375b51290e102.tar.zst +0 -0
  76. package/.turbo/cache/e5310fe547fdbf0a-meta.json +1 -0
  77. package/.turbo/cache/e5310fe547fdbf0a.tar.zst +0 -0
  78. package/.turbo/cache/f12bb5f2f188758d-meta.json +1 -0
  79. package/.turbo/cache/f12bb5f2f188758d.tar.zst +0 -0
  80. package/.turbo/cache/f2db5af0c0b4d23f-meta.json +1 -0
  81. package/.turbo/cache/f2db5af0c0b4d23f.tar.zst +0 -0
  82. package/.turbo/cache/f8935ade01a88cd7-meta.json +1 -0
  83. package/.turbo/cache/f8935ade01a88cd7.tar.zst +0 -0
  84. package/.turbo/cache/f982b8dd966f823a-meta.json +1 -0
  85. package/.turbo/cache/f982b8dd966f823a.tar.zst +0 -0
  86. package/.turbo/cache/f9d4036dd350ba1a-meta.json +1 -0
  87. package/.turbo/cache/f9d4036dd350ba1a.tar.zst +0 -0
  88. package/README.md +661 -0
  89. package/README_ko.md +577 -0
  90. package/bun.lock +135 -0
  91. package/package.json +26 -0
  92. package/packages/agent/.turbo/turbo-build.log +5 -0
  93. package/packages/agent/.turbo/turbo-typecheck.log +1 -0
  94. package/packages/agent/dist/connection/hub-client.d.ts +33 -0
  95. package/packages/agent/dist/connection/hub-client.d.ts.map +1 -0
  96. package/packages/agent/dist/connection/index.d.ts +2 -0
  97. package/packages/agent/dist/connection/index.d.ts.map +1 -0
  98. package/packages/agent/dist/hooks/index.d.ts +3 -0
  99. package/packages/agent/dist/hooks/index.d.ts.map +1 -0
  100. package/packages/agent/dist/hooks/intent-hub-hooks.d.ts +47 -0
  101. package/packages/agent/dist/hooks/intent-hub-hooks.d.ts.map +1 -0
  102. package/packages/agent/dist/index.d.ts +6 -0
  103. package/packages/agent/dist/index.d.ts.map +1 -0
  104. package/packages/agent/dist/index.js +3315 -0
  105. package/packages/agent/dist/plugin/index.d.ts +3 -0
  106. package/packages/agent/dist/plugin/index.d.ts.map +1 -0
  107. package/packages/agent/dist/plugin/intent-hub-plugin.d.ts +54 -0
  108. package/packages/agent/dist/plugin/intent-hub-plugin.d.ts.map +1 -0
  109. package/packages/agent/package.json +32 -0
  110. package/packages/agent/src/connection/hub-client.ts +152 -0
  111. package/packages/agent/src/connection/index.ts +1 -0
  112. package/packages/agent/src/hooks/index.ts +2 -0
  113. package/packages/agent/src/hooks/intent-hub-hooks.ts +245 -0
  114. package/packages/agent/src/index.ts +5 -0
  115. package/packages/agent/src/plugin/index.ts +2 -0
  116. package/packages/agent/src/plugin/intent-hub-plugin.ts +153 -0
  117. package/packages/agent/tsconfig.json +9 -0
  118. package/packages/hub/.turbo/turbo-build.log +6 -0
  119. package/packages/hub/.turbo/turbo-typecheck.log +1 -0
  120. package/packages/hub/dist/api/dashboard.d.ts +17 -0
  121. package/packages/hub/dist/api/dashboard.d.ts.map +1 -0
  122. package/packages/hub/dist/cli.d.ts +3 -0
  123. package/packages/hub/dist/cli.d.ts.map +1 -0
  124. package/packages/hub/dist/cli.js +7719 -0
  125. package/packages/hub/dist/core/conflict-detector.d.ts +36 -0
  126. package/packages/hub/dist/core/conflict-detector.d.ts.map +1 -0
  127. package/packages/hub/dist/core/index.d.ts +7 -0
  128. package/packages/hub/dist/core/index.d.ts.map +1 -0
  129. package/packages/hub/dist/core/intent-analyzer.d.ts +8 -0
  130. package/packages/hub/dist/core/intent-analyzer.d.ts.map +1 -0
  131. package/packages/hub/dist/core/lock-manager.d.ts +13 -0
  132. package/packages/hub/dist/core/lock-manager.d.ts.map +1 -0
  133. package/packages/hub/dist/core/orchestrator.d.ts +46 -0
  134. package/packages/hub/dist/core/orchestrator.d.ts.map +1 -0
  135. package/packages/hub/dist/index.d.ts +9 -0
  136. package/packages/hub/dist/index.d.ts.map +1 -0
  137. package/packages/hub/dist/index.js +4686 -0
  138. package/packages/hub/dist/llm/index.d.ts +7 -0
  139. package/packages/hub/dist/llm/index.d.ts.map +1 -0
  140. package/packages/hub/dist/llm/negotiation-engine.d.ts +40 -0
  141. package/packages/hub/dist/llm/negotiation-engine.d.ts.map +1 -0
  142. package/packages/hub/dist/llm/provider.d.ts +46 -0
  143. package/packages/hub/dist/llm/provider.d.ts.map +1 -0
  144. package/packages/hub/dist/llm/smart-analyzer.d.ts +20 -0
  145. package/packages/hub/dist/llm/smart-analyzer.d.ts.map +1 -0
  146. package/packages/hub/dist/server/hub-server.d.ts +35 -0
  147. package/packages/hub/dist/server/hub-server.d.ts.map +1 -0
  148. package/packages/hub/dist/server/index.d.ts +5 -0
  149. package/packages/hub/dist/server/index.d.ts.map +1 -0
  150. package/packages/hub/dist/server/message-handler.d.ts +18 -0
  151. package/packages/hub/dist/server/message-handler.d.ts.map +1 -0
  152. package/packages/hub/dist/server/smart-hub-server.d.ts +43 -0
  153. package/packages/hub/dist/server/smart-hub-server.d.ts.map +1 -0
  154. package/packages/hub/dist/state/index.d.ts +2 -0
  155. package/packages/hub/dist/state/index.d.ts.map +1 -0
  156. package/packages/hub/dist/state/session-manager.d.ts +19 -0
  157. package/packages/hub/dist/state/session-manager.d.ts.map +1 -0
  158. package/packages/hub/dist/tunnel/index.d.ts +14 -0
  159. package/packages/hub/dist/tunnel/index.d.ts.map +1 -0
  160. package/packages/hub/package.json +54 -0
  161. package/packages/hub/src/api/dashboard.ts +261 -0
  162. package/packages/hub/src/cli.ts +193 -0
  163. package/packages/hub/src/core/conflict-detector.ts +138 -0
  164. package/packages/hub/src/core/index.ts +6 -0
  165. package/packages/hub/src/core/intent-analyzer.ts +112 -0
  166. package/packages/hub/src/core/lock-manager.ts +95 -0
  167. package/packages/hub/src/core/orchestrator.ts +255 -0
  168. package/packages/hub/src/index.ts +8 -0
  169. package/packages/hub/src/llm/index.ts +17 -0
  170. package/packages/hub/src/llm/negotiation-engine.ts +297 -0
  171. package/packages/hub/src/llm/provider.ts +175 -0
  172. package/packages/hub/src/llm/smart-analyzer.ts +169 -0
  173. package/packages/hub/src/server/hub-server.ts +219 -0
  174. package/packages/hub/src/server/index.ts +4 -0
  175. package/packages/hub/src/server/message-handler.ts +111 -0
  176. package/packages/hub/src/server/smart-hub-server.ts +374 -0
  177. package/packages/hub/src/state/index.ts +1 -0
  178. package/packages/hub/src/state/session-manager.ts +59 -0
  179. package/packages/hub/src/tunnel/index.ts +153 -0
  180. package/packages/hub/tsconfig.json +9 -0
  181. package/packages/shared/.turbo/turbo-build.log +5 -0
  182. package/packages/shared/.turbo/turbo-typecheck.log +1 -0
  183. package/packages/shared/dist/index.d.ts +3 -0
  184. package/packages/shared/dist/index.d.ts.map +1 -0
  185. package/packages/shared/dist/index.js +50 -0
  186. package/packages/shared/dist/types/domain.d.ts +50 -0
  187. package/packages/shared/dist/types/domain.d.ts.map +1 -0
  188. package/packages/shared/dist/types/index.d.ts +4 -0
  189. package/packages/shared/dist/types/index.d.ts.map +1 -0
  190. package/packages/shared/dist/types/intent.d.ts +24 -0
  191. package/packages/shared/dist/types/intent.d.ts.map +1 -0
  192. package/packages/shared/dist/types/message.d.ts +151 -0
  193. package/packages/shared/dist/types/message.d.ts.map +1 -0
  194. package/packages/shared/dist/utils/id.d.ts +6 -0
  195. package/packages/shared/dist/utils/id.d.ts.map +1 -0
  196. package/packages/shared/dist/utils/index.d.ts +3 -0
  197. package/packages/shared/dist/utils/index.d.ts.map +1 -0
  198. package/packages/shared/dist/utils/message.d.ts +5 -0
  199. package/packages/shared/dist/utils/message.d.ts.map +1 -0
  200. package/packages/shared/package.json +33 -0
  201. package/packages/shared/src/index.ts +2 -0
  202. package/packages/shared/src/types/domain.ts +57 -0
  203. package/packages/shared/src/types/index.ts +3 -0
  204. package/packages/shared/src/types/intent.ts +34 -0
  205. package/packages/shared/src/types/message.ts +188 -0
  206. package/packages/shared/src/utils/id.ts +21 -0
  207. package/packages/shared/src/utils/index.ts +2 -0
  208. package/packages/shared/src/utils/message.ts +30 -0
  209. package/packages/shared/tsconfig.json +9 -0
  210. package/scripts/test-e2e.ts +194 -0
  211. package/scripts/test-mvp2.ts +167 -0
  212. package/scripts/test-mvp3.ts +405 -0
  213. package/tsconfig.json +19 -0
  214. package/turbo.json +22 -0
@@ -0,0 +1,374 @@
1
+ import { WebSocketServer, WebSocket } from 'ws';
2
+ import type {
3
+ AgentMessage,
4
+ HubConnectedMessage,
5
+ HubIntentReceivedMessage,
6
+ HubIntentAnalyzedMessage,
7
+ HubLockAcquiredMessage,
8
+ HubLockBlockedMessage,
9
+ HubNegotiationStartMessage,
10
+ HubNegotiationResolvedMessage,
11
+ HubDecisionRequiredMessage,
12
+ BroadcastUserJoinedMessage,
13
+ BroadcastUserLeftMessage,
14
+ BroadcastLockUpdateMessage,
15
+ } from '@anthropic-for-korea/intent-hub-shared';
16
+ import {
17
+ parseMessage,
18
+ serializeMessage,
19
+ createMessage,
20
+ generateSessionId,
21
+ } from '@anthropic-for-korea/intent-hub-shared';
22
+ import { SessionManager } from '../state/session-manager.js';
23
+ import { Orchestrator, type OrchestratorConfig, type IntentResponse } from '../core/orchestrator.js';
24
+ import type { LLMProvider } from '../llm/provider.js';
25
+
26
+ export interface SmartHubServerOptions {
27
+ port: number;
28
+ host?: string;
29
+ llmProvider: LLMProvider;
30
+ orchestratorConfig?: Partial<OrchestratorConfig>;
31
+ }
32
+
33
+ export interface ConnectedClient {
34
+ socket: WebSocket;
35
+ sessionId: string;
36
+ userId: string;
37
+ username: string;
38
+ projectPath: string;
39
+ connectedAt: number;
40
+ }
41
+
42
+ export class SmartHubServer {
43
+ private wss: WebSocketServer | null = null;
44
+ private clients: Map<string, ConnectedClient> = new Map();
45
+ private sessionManager: SessionManager;
46
+ private orchestrator: Orchestrator;
47
+
48
+ constructor(private options: SmartHubServerOptions) {
49
+ this.sessionManager = new SessionManager();
50
+
51
+ const orchestratorConfig: OrchestratorConfig = {
52
+ useLLMAnalysis: options.orchestratorConfig?.useLLMAnalysis ?? true,
53
+ autoNegotiate: options.orchestratorConfig?.autoNegotiate ?? true,
54
+ conflictThreshold: options.orchestratorConfig?.conflictThreshold ?? 'medium',
55
+ };
56
+
57
+ this.orchestrator = new Orchestrator(
58
+ options.llmProvider,
59
+ orchestratorConfig,
60
+ this.sendNotification.bind(this)
61
+ );
62
+ }
63
+
64
+ start(): void {
65
+ this.wss = new WebSocketServer({
66
+ port: this.options.port,
67
+ host: this.options.host ?? '0.0.0.0',
68
+ });
69
+
70
+ this.wss.on('connection', (socket) => {
71
+ this.handleConnection(socket);
72
+ });
73
+
74
+ this.wss.on('error', (error) => {
75
+ console.error('[SmartHub] Server error:', error);
76
+ });
77
+
78
+ console.log(`[SmartHub] Server started on ${this.options.host ?? '0.0.0.0'}:${this.options.port}`);
79
+ }
80
+
81
+ stop(): void {
82
+ if (this.wss) {
83
+ for (const client of this.clients.values()) {
84
+ client.socket.close();
85
+ }
86
+ this.clients.clear();
87
+ this.wss.close();
88
+ this.wss = null;
89
+ console.log('[SmartHub] Server stopped');
90
+ }
91
+ }
92
+
93
+ private handleConnection(socket: WebSocket): void {
94
+ const tempSessionId = generateSessionId();
95
+
96
+ socket.on('message', async (data) => {
97
+ const message = parseMessage(data.toString());
98
+ if (!message) {
99
+ console.warn('[SmartHub] Invalid message received');
100
+ return;
101
+ }
102
+
103
+ await this.handleMessage(socket, tempSessionId, message as AgentMessage);
104
+ });
105
+
106
+ socket.on('close', () => {
107
+ this.handleDisconnection(socket);
108
+ });
109
+
110
+ socket.on('error', (error) => {
111
+ console.error(`[SmartHub] Socket error:`, error);
112
+ });
113
+ }
114
+
115
+ private async handleMessage(socket: WebSocket, tempSessionId: string, message: AgentMessage): Promise<void> {
116
+ if (message.type === 'agent:connect') {
117
+ const sessionId = generateSessionId();
118
+ const client: ConnectedClient = {
119
+ socket,
120
+ sessionId,
121
+ userId: message.payload.userId,
122
+ username: message.payload.username,
123
+ projectPath: message.payload.projectPath,
124
+ connectedAt: Date.now(),
125
+ };
126
+
127
+ this.clients.set(sessionId, client);
128
+ this.sessionManager.addSession(sessionId, client.userId, client.username);
129
+
130
+ const status = this.orchestrator.getStatus();
131
+ const response = createMessage<HubConnectedMessage>('hub:connected', {
132
+ payload: {
133
+ sessionId,
134
+ activeLocks: [],
135
+ },
136
+ });
137
+ this.sendToClient(sessionId, response);
138
+
139
+ const joinBroadcast = createMessage<BroadcastUserJoinedMessage>('broadcast:user:joined', {
140
+ payload: {
141
+ userId: client.userId,
142
+ username: client.username,
143
+ },
144
+ });
145
+ this.broadcastExcept(sessionId, joinBroadcast);
146
+
147
+ console.log(`[SmartHub] Client connected: ${client.username} (${sessionId})`);
148
+ return;
149
+ }
150
+
151
+ const client = this.findClientBySocket(socket);
152
+ if (!client) {
153
+ console.warn('[SmartHub] Message from unknown client');
154
+ return;
155
+ }
156
+
157
+ if (message.type === 'agent:intent:submit') {
158
+ await this.handleIntentSubmit(client, message.payload.prompt);
159
+ } else if (message.type === 'agent:decision') {
160
+ console.log(`[SmartHub] Decision from ${client.username}: ${message.payload.choice}`);
161
+ }
162
+ }
163
+
164
+ private async handleIntentSubmit(client: ConnectedClient, prompt: string): Promise<void> {
165
+ const receivedMsg = createMessage<HubIntentReceivedMessage>('hub:intent:received', {
166
+ payload: { intentId: 'pending' },
167
+ });
168
+ this.sendToClient(client.sessionId, receivedMsg);
169
+
170
+ try {
171
+ const response = await this.orchestrator.processIntent({
172
+ sessionId: client.sessionId,
173
+ userId: client.userId,
174
+ username: client.username,
175
+ prompt,
176
+ });
177
+
178
+ this.sendIntentResponse(client, response);
179
+ this.broadcastLockUpdate();
180
+
181
+ } catch (error) {
182
+ console.error('[SmartHub] Error processing intent:', error);
183
+ }
184
+ }
185
+
186
+ private sendIntentResponse(client: ConnectedClient, response: IntentResponse): void {
187
+ const analyzedMsg = createMessage<HubIntentAnalyzedMessage>('hub:intent:analyzed', {
188
+ payload: {
189
+ intentId: response.intentId,
190
+ analysis: {
191
+ domain: response.analysis.domain,
192
+ subdomain: response.analysis.subdomain,
193
+ affectedFiles: response.analysis.affectedFiles,
194
+ semanticTags: response.analysis.semanticTags,
195
+ },
196
+ },
197
+ });
198
+ this.sendToClient(client.sessionId, analyzedMsg);
199
+
200
+ if (response.status === 'approved') {
201
+ const acquiredMsg = createMessage<HubLockAcquiredMessage>('hub:lock:acquired', {
202
+ payload: {
203
+ intentId: response.intentId,
204
+ domains: response.domains,
205
+ },
206
+ });
207
+ this.sendToClient(client.sessionId, acquiredMsg);
208
+ console.log(`[SmartHub] ${client.username} acquired lock on ${response.domains.join(', ')}`);
209
+ }
210
+ else if (response.status === 'blocked') {
211
+ const blockedMsg = createMessage<HubLockBlockedMessage>('hub:lock:blocked', {
212
+ payload: {
213
+ intentId: response.intentId,
214
+ blockedBy: (response.blockedBy || []).map(b => ({
215
+ domain: b.domain,
216
+ username: b.username,
217
+ intentSummary: b.reason,
218
+ })),
219
+ },
220
+ });
221
+ this.sendToClient(client.sessionId, blockedMsg);
222
+ console.log(`[SmartHub] ${client.username} blocked: ${response.blockedBy?.map(b => b.reason).join(', ')}`);
223
+ }
224
+ else if (response.status === 'negotiating' && response.negotiation) {
225
+ if (response.negotiation.status === 'agreed') {
226
+ const resolvedMsg = createMessage<HubNegotiationResolvedMessage>('hub:negotiation:resolved', {
227
+ payload: {
228
+ conflictId: response.negotiation.conflictId,
229
+ resolution: {
230
+ type: 'auto',
231
+ summary: response.negotiation.proposals.map(p => p.proposal).join(' / '),
232
+ yourPhase: response.negotiation.plan?.phases.findIndex(p => p.userId === client.userId) ?? 0,
233
+ },
234
+ },
235
+ });
236
+ this.sendToClient(client.sessionId, resolvedMsg);
237
+ }
238
+ }
239
+ else if (response.status === 'pending_decision' && response.negotiation) {
240
+ const decisionMsg = createMessage<HubDecisionRequiredMessage>('hub:decision:required', {
241
+ payload: {
242
+ conflictId: response.negotiation.conflictId,
243
+ question: response.negotiation.disagreementReason || 'How should this conflict be resolved?',
244
+ options: response.negotiation.options || [],
245
+ },
246
+ });
247
+ this.sendToClient(client.sessionId, decisionMsg);
248
+ }
249
+ }
250
+
251
+ private sendNotification(sessionId: string, event: string, data: any): void {
252
+ const client = this.clients.get(sessionId);
253
+ if (!client) {
254
+ for (const [sid, c] of this.clients) {
255
+ if (c.sessionId === sessionId || c.userId === sessionId) {
256
+ this.sendNotificationToClient(c, event, data);
257
+ return;
258
+ }
259
+ }
260
+ return;
261
+ }
262
+ this.sendNotificationToClient(client, event, data);
263
+ }
264
+
265
+ private sendNotificationToClient(client: ConnectedClient, event: string, data: any): void {
266
+ if (event === 'negotiation:start') {
267
+ const msg = createMessage<HubNegotiationStartMessage>('hub:negotiation:start', {
268
+ payload: {
269
+ conflictId: data.conflictId,
270
+ participants: [data.withUser],
271
+ domains: [],
272
+ },
273
+ });
274
+ this.sendToClient(client.sessionId, msg);
275
+ } else if (event === 'negotiation:resolved') {
276
+ const msg = createMessage<HubNegotiationResolvedMessage>('hub:negotiation:resolved', {
277
+ payload: {
278
+ conflictId: data.conflictId,
279
+ resolution: {
280
+ type: 'auto',
281
+ summary: data.message,
282
+ yourPhase: data.result === 'proceed' ? 1 : 2,
283
+ },
284
+ },
285
+ });
286
+ this.sendToClient(client.sessionId, msg);
287
+ } else if (event === 'decision:required') {
288
+ const msg = createMessage<HubDecisionRequiredMessage>('hub:decision:required', {
289
+ payload: {
290
+ conflictId: data.conflictId,
291
+ question: data.reason,
292
+ options: data.options,
293
+ },
294
+ });
295
+ this.sendToClient(client.sessionId, msg);
296
+ }
297
+ }
298
+
299
+ private broadcastLockUpdate(): void {
300
+ const status = this.orchestrator.getStatus();
301
+ const msg = createMessage<BroadcastLockUpdateMessage>('broadcast:lock:update', {
302
+ payload: {
303
+ locks: [],
304
+ },
305
+ });
306
+ this.broadcast(msg);
307
+ }
308
+
309
+ private handleDisconnection(socket: WebSocket): void {
310
+ for (const [sessionId, client] of this.clients) {
311
+ if (client.socket === socket) {
312
+ this.sessionManager.removeSession(sessionId);
313
+ this.clients.delete(sessionId);
314
+
315
+ const leaveBroadcast = createMessage<BroadcastUserLeftMessage>('broadcast:user:left', {
316
+ payload: {
317
+ userId: client.userId,
318
+ username: client.username,
319
+ },
320
+ });
321
+ this.broadcast(leaveBroadcast);
322
+
323
+ console.log(`[SmartHub] Client disconnected: ${client.username} (${sessionId})`);
324
+ return;
325
+ }
326
+ }
327
+ }
328
+
329
+ private findClientBySocket(socket: WebSocket): ConnectedClient | undefined {
330
+ for (const client of this.clients.values()) {
331
+ if (client.socket === socket) {
332
+ return client;
333
+ }
334
+ }
335
+ return undefined;
336
+ }
337
+
338
+ private sendToClient(sessionId: string, message: any): void {
339
+ const client = this.clients.get(sessionId);
340
+ if (client && client.socket.readyState === WebSocket.OPEN) {
341
+ client.socket.send(serializeMessage(message));
342
+ }
343
+ }
344
+
345
+ private broadcast(message: any): void {
346
+ const serialized = serializeMessage(message);
347
+ for (const client of this.clients.values()) {
348
+ if (client.socket.readyState === WebSocket.OPEN) {
349
+ client.socket.send(serialized);
350
+ }
351
+ }
352
+ }
353
+
354
+ private broadcastExcept(excludeSessionId: string, message: any): void {
355
+ const serialized = serializeMessage(message);
356
+ for (const [sessionId, client] of this.clients) {
357
+ if (sessionId !== excludeSessionId && client.socket.readyState === WebSocket.OPEN) {
358
+ client.socket.send(serialized);
359
+ }
360
+ }
361
+ }
362
+
363
+ getConnectedClients(): ConnectedClient[] {
364
+ return Array.from(this.clients.values());
365
+ }
366
+
367
+ getClientCount(): number {
368
+ return this.clients.size;
369
+ }
370
+
371
+ getOrchestratorStatus(): ReturnType<Orchestrator['getStatus']> {
372
+ return this.orchestrator.getStatus();
373
+ }
374
+ }
@@ -0,0 +1 @@
1
+ export { SessionManager } from './session-manager.js';
@@ -0,0 +1,59 @@
1
+ export interface Session {
2
+ sessionId: string;
3
+ userId: string;
4
+ username: string;
5
+ connectedAt: number;
6
+ lastActiveAt: number;
7
+ }
8
+
9
+ export class SessionManager {
10
+ private sessions: Map<string, Session> = new Map();
11
+
12
+ addSession(sessionId: string, userId: string, username: string): Session {
13
+ const session: Session = {
14
+ sessionId,
15
+ userId,
16
+ username,
17
+ connectedAt: Date.now(),
18
+ lastActiveAt: Date.now(),
19
+ };
20
+ this.sessions.set(sessionId, session);
21
+ return session;
22
+ }
23
+
24
+ removeSession(sessionId: string): boolean {
25
+ return this.sessions.delete(sessionId);
26
+ }
27
+
28
+ getSession(sessionId: string): Session | undefined {
29
+ return this.sessions.get(sessionId);
30
+ }
31
+
32
+ getSessionByUserId(userId: string): Session | undefined {
33
+ for (const session of this.sessions.values()) {
34
+ if (session.userId === userId) {
35
+ return session;
36
+ }
37
+ }
38
+ return undefined;
39
+ }
40
+
41
+ getAllSessions(): Session[] {
42
+ return Array.from(this.sessions.values());
43
+ }
44
+
45
+ updateLastActive(sessionId: string): void {
46
+ const session = this.sessions.get(sessionId);
47
+ if (session) {
48
+ session.lastActiveAt = Date.now();
49
+ }
50
+ }
51
+
52
+ getActiveUserCount(): number {
53
+ return this.sessions.size;
54
+ }
55
+
56
+ getActiveUsernames(): string[] {
57
+ return Array.from(this.sessions.values()).map(s => s.username);
58
+ }
59
+ }
@@ -0,0 +1,153 @@
1
+ import { spawn } from 'child_process';
2
+ import { randomBytes } from 'crypto';
3
+
4
+ export interface TunnelOptions {
5
+ port: number;
6
+ subdomain?: string;
7
+ provider?: 'localtunnel' | 'cloudflared' | 'auto';
8
+ }
9
+
10
+ export interface TunnelResult {
11
+ url: string;
12
+ wsUrl: string;
13
+ provider: string;
14
+ close: () => Promise<void>;
15
+ }
16
+
17
+ function generateSubdomain(): string {
18
+ const id = randomBytes(4).toString('hex');
19
+ return `intent-hub-${id}`;
20
+ }
21
+
22
+ async function commandExists(cmd: string): Promise<boolean> {
23
+ return new Promise((resolve) => {
24
+ const process = spawn('which', [cmd], { stdio: 'ignore' });
25
+ process.on('close', (code) => resolve(code === 0));
26
+ process.on('error', () => resolve(false));
27
+ });
28
+ }
29
+
30
+ async function createLocaltunnel(options: TunnelOptions): Promise<TunnelResult> {
31
+ const localtunnel = await import('localtunnel').then(m => m.default);
32
+
33
+ const subdomain = options.subdomain ?? generateSubdomain();
34
+
35
+ const tunnel = await localtunnel({
36
+ port: options.port,
37
+ subdomain,
38
+ allow_invalid_cert: true,
39
+ });
40
+
41
+ const url = tunnel.url;
42
+ const wsUrl = url.replace('https://', 'wss://').replace('http://', 'ws://');
43
+
44
+ return {
45
+ url,
46
+ wsUrl,
47
+ provider: 'localtunnel',
48
+ close: async () => {
49
+ tunnel.close();
50
+ },
51
+ };
52
+ }
53
+
54
+ async function createCloudflaredTunnel(options: TunnelOptions): Promise<TunnelResult> {
55
+ return new Promise((resolve, reject) => {
56
+ const process = spawn('cloudflared', [
57
+ 'tunnel',
58
+ '--url',
59
+ `http://localhost:${options.port}`,
60
+ '--no-autoupdate',
61
+ ], {
62
+ stdio: ['ignore', 'pipe', 'pipe'],
63
+ });
64
+
65
+ let resolved = false;
66
+
67
+ const handleOutput = (data: Buffer) => {
68
+ const output = data.toString();
69
+ const match = output.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/);
70
+ if (match && !resolved) {
71
+ resolved = true;
72
+ const url = match[0];
73
+ const wsUrl = url.replace('https://', 'wss://');
74
+ resolve({
75
+ url,
76
+ wsUrl,
77
+ provider: 'cloudflared',
78
+ close: async () => {
79
+ process.kill('SIGTERM');
80
+ },
81
+ });
82
+ }
83
+ };
84
+
85
+ process.stdout?.on('data', handleOutput);
86
+ process.stderr?.on('data', handleOutput);
87
+
88
+ process.on('error', (err) => {
89
+ if (!resolved) {
90
+ reject(new Error(`Failed to start cloudflared: ${err.message}`));
91
+ }
92
+ });
93
+
94
+ process.on('close', (code) => {
95
+ if (!resolved) {
96
+ reject(new Error(`cloudflared exited with code ${code}`));
97
+ }
98
+ });
99
+
100
+ setTimeout(() => {
101
+ if (!resolved) {
102
+ process.kill('SIGTERM');
103
+ reject(new Error('Timeout waiting for cloudflared tunnel'));
104
+ }
105
+ }, 30000);
106
+ });
107
+ }
108
+
109
+ export async function createTunnel(options: TunnelOptions): Promise<TunnelResult> {
110
+ const provider = options.provider ?? 'auto';
111
+
112
+ if (provider === 'cloudflared' || provider === 'auto') {
113
+ const hasCloudflared = await commandExists('cloudflared');
114
+ if (hasCloudflared) {
115
+ try {
116
+ return await createCloudflaredTunnel(options);
117
+ } catch (err) {
118
+ if (provider === 'cloudflared') {
119
+ throw err;
120
+ }
121
+ console.log('[Tunnel] cloudflared failed, falling back to localtunnel');
122
+ }
123
+ }
124
+ }
125
+
126
+ if (provider === 'localtunnel' || provider === 'auto') {
127
+ try {
128
+ return await createLocaltunnel(options);
129
+ } catch (err) {
130
+ throw new Error(`Failed to create tunnel: ${err}`);
131
+ }
132
+ }
133
+
134
+ throw new Error(`Unknown tunnel provider: ${provider}`);
135
+ }
136
+
137
+ export function printShareInfo(tunnel: TunnelResult): void {
138
+ console.log(`
139
+ ╔═══════════════════════════════════════════════════════════════╗
140
+ ║ 🔗 SHARE YOUR SESSION ║
141
+ ╠═══════════════════════════════════════════════════════════════╣
142
+ ║ ║
143
+ ║ Team members can connect using: ║
144
+ ║ ║
145
+ ║ ${tunnel.wsUrl.padEnd(61)}║
146
+ ║ ║
147
+ ║ Provider: ${tunnel.provider.padEnd(52)}║
148
+ ║ ║
149
+ ║ Copy and share this URL with your team! ║
150
+ ║ ║
151
+ ╚═══════════════════════════════════════════════════════════════╝
152
+ `);
153
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }
@@ -0,0 +1,5 @@
1
+ $ bun build ./src/index.ts --outdir ./dist --target node && tsc --emitDeclarationOnly
2
+ Bundled 8 modules in 2ms
3
+
4
+ index.js 1.0 KB (entry point)
5
+
@@ -0,0 +1 @@
1
+ $ tsc --noEmit
@@ -0,0 +1,3 @@
1
+ export * from './types/index.js';
2
+ export * from './utils/index.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,50 @@
1
+ // src/utils/id.ts
2
+ import { randomUUID } from "crypto";
3
+ function generateId() {
4
+ return randomUUID();
5
+ }
6
+ function generateMessageId() {
7
+ return `msg_${randomUUID()}`;
8
+ }
9
+ function generateIntentId() {
10
+ return `int_${randomUUID()}`;
11
+ }
12
+ function generateConflictId() {
13
+ return `cnf_${randomUUID()}`;
14
+ }
15
+ function generateSessionId() {
16
+ return `ses_${randomUUID()}`;
17
+ }
18
+ // src/utils/message.ts
19
+ function createMessage(type, payload) {
20
+ return {
21
+ type,
22
+ timestamp: Date.now(),
23
+ messageId: generateMessageId(),
24
+ ...payload
25
+ };
26
+ }
27
+ function parseMessage(data) {
28
+ try {
29
+ const parsed = JSON.parse(data);
30
+ if (typeof parsed.type === "string" && typeof parsed.timestamp === "number") {
31
+ return parsed;
32
+ }
33
+ return null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+ function serializeMessage(message2) {
39
+ return JSON.stringify(message2);
40
+ }
41
+ export {
42
+ serializeMessage,
43
+ parseMessage,
44
+ generateSessionId,
45
+ generateMessageId,
46
+ generateIntentId,
47
+ generateId,
48
+ generateConflictId,
49
+ createMessage
50
+ };