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,175 @@
1
+ export interface LLMMessage {
2
+ role: 'system' | 'user' | 'assistant';
3
+ content: string;
4
+ }
5
+
6
+ export interface LLMCompletionOptions {
7
+ model?: string;
8
+ temperature?: number;
9
+ maxTokens?: number;
10
+ }
11
+
12
+ export interface LLMCompletionResult {
13
+ content: string;
14
+ model: string;
15
+ usage?: {
16
+ promptTokens: number;
17
+ completionTokens: number;
18
+ totalTokens: number;
19
+ };
20
+ }
21
+
22
+ export interface LLMProvider {
23
+ name: string;
24
+ complete(messages: LLMMessage[], options?: LLMCompletionOptions): Promise<LLMCompletionResult>;
25
+ completeJSON<T>(messages: LLMMessage[], options?: LLMCompletionOptions): Promise<T>;
26
+ }
27
+
28
+ export class OpenAIProvider implements LLMProvider {
29
+ name = 'openai';
30
+
31
+ constructor(private apiKey: string, private defaultModel = 'gpt-4o-mini') {}
32
+
33
+ async complete(messages: LLMMessage[], options?: LLMCompletionOptions): Promise<LLMCompletionResult> {
34
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
35
+ method: 'POST',
36
+ headers: {
37
+ 'Content-Type': 'application/json',
38
+ 'Authorization': `Bearer ${this.apiKey}`,
39
+ },
40
+ body: JSON.stringify({
41
+ model: options?.model ?? this.defaultModel,
42
+ messages,
43
+ temperature: options?.temperature ?? 0.7,
44
+ max_tokens: options?.maxTokens ?? 2048,
45
+ }),
46
+ });
47
+
48
+ if (!response.ok) {
49
+ const error = await response.text();
50
+ throw new Error(`OpenAI API error: ${response.status} - ${error}`);
51
+ }
52
+
53
+ const data = await response.json() as any;
54
+ return {
55
+ content: data.choices[0].message.content,
56
+ model: data.model,
57
+ usage: data.usage ? {
58
+ promptTokens: data.usage.prompt_tokens,
59
+ completionTokens: data.usage.completion_tokens,
60
+ totalTokens: data.usage.total_tokens,
61
+ } : undefined,
62
+ };
63
+ }
64
+
65
+ async completeJSON<T>(messages: LLMMessage[], options?: LLMCompletionOptions): Promise<T> {
66
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
67
+ method: 'POST',
68
+ headers: {
69
+ 'Content-Type': 'application/json',
70
+ 'Authorization': `Bearer ${this.apiKey}`,
71
+ },
72
+ body: JSON.stringify({
73
+ model: options?.model ?? this.defaultModel,
74
+ messages,
75
+ temperature: options?.temperature ?? 0.3,
76
+ max_tokens: options?.maxTokens ?? 2048,
77
+ response_format: { type: 'json_object' },
78
+ }),
79
+ });
80
+
81
+ if (!response.ok) {
82
+ const error = await response.text();
83
+ throw new Error(`OpenAI API error: ${response.status} - ${error}`);
84
+ }
85
+
86
+ const data = await response.json() as any;
87
+ return JSON.parse(data.choices[0].message.content) as T;
88
+ }
89
+ }
90
+
91
+ export class AnthropicProvider implements LLMProvider {
92
+ name = 'anthropic';
93
+
94
+ constructor(private apiKey: string, private defaultModel = 'claude-3-5-sonnet-20241022') {}
95
+
96
+ async complete(messages: LLMMessage[], options?: LLMCompletionOptions): Promise<LLMCompletionResult> {
97
+ const systemMessage = messages.find(m => m.role === 'system');
98
+ const otherMessages = messages.filter(m => m.role !== 'system');
99
+
100
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
101
+ method: 'POST',
102
+ headers: {
103
+ 'Content-Type': 'application/json',
104
+ 'x-api-key': this.apiKey,
105
+ 'anthropic-version': '2023-06-01',
106
+ },
107
+ body: JSON.stringify({
108
+ model: options?.model ?? this.defaultModel,
109
+ max_tokens: options?.maxTokens ?? 2048,
110
+ system: systemMessage?.content,
111
+ messages: otherMessages.map(m => ({
112
+ role: m.role,
113
+ content: m.content,
114
+ })),
115
+ }),
116
+ });
117
+
118
+ if (!response.ok) {
119
+ const error = await response.text();
120
+ throw new Error(`Anthropic API error: ${response.status} - ${error}`);
121
+ }
122
+
123
+ const data = await response.json() as any;
124
+ return {
125
+ content: data.content[0].text,
126
+ model: data.model,
127
+ usage: data.usage ? {
128
+ promptTokens: data.usage.input_tokens,
129
+ completionTokens: data.usage.output_tokens,
130
+ totalTokens: data.usage.input_tokens + data.usage.output_tokens,
131
+ } : undefined,
132
+ };
133
+ }
134
+
135
+ async completeJSON<T>(messages: LLMMessage[], options?: LLMCompletionOptions): Promise<T> {
136
+ const result = await this.complete([
137
+ ...messages,
138
+ { role: 'assistant', content: '{' },
139
+ ], options);
140
+
141
+ const jsonStr = '{' + result.content;
142
+ return JSON.parse(jsonStr) as T;
143
+ }
144
+ }
145
+
146
+ export class MockProvider implements LLMProvider {
147
+ name = 'mock';
148
+
149
+ async complete(messages: LLMMessage[]): Promise<LLMCompletionResult> {
150
+ const lastMessage = messages[messages.length - 1];
151
+ return {
152
+ content: `Mock response to: ${lastMessage?.content.slice(0, 50)}...`,
153
+ model: 'mock-model',
154
+ };
155
+ }
156
+
157
+ async completeJSON<T>(): Promise<T> {
158
+ return {} as T;
159
+ }
160
+ }
161
+
162
+ export function createProvider(type: 'openai' | 'anthropic' | 'mock', apiKey?: string): LLMProvider {
163
+ switch (type) {
164
+ case 'openai':
165
+ if (!apiKey) throw new Error('OpenAI API key required');
166
+ return new OpenAIProvider(apiKey);
167
+ case 'anthropic':
168
+ if (!apiKey) throw new Error('Anthropic API key required');
169
+ return new AnthropicProvider(apiKey);
170
+ case 'mock':
171
+ return new MockProvider();
172
+ default:
173
+ throw new Error(`Unknown provider type: ${type}`);
174
+ }
175
+ }
@@ -0,0 +1,169 @@
1
+ import type { LLMProvider, LLMMessage } from './provider.js';
2
+ import type { IntentAnalysis } from '@anthropic-for-korea/intent-hub-shared';
3
+
4
+ const INTENT_ANALYSIS_PROMPT = `You are an intent analyzer for a team collaboration system.
5
+ Analyze the user's request and extract structured information about what they want to do.
6
+
7
+ Respond in JSON format with the following structure:
8
+ {
9
+ "domain": "main domain (e.g., auth, user, payment, database, api, ui, test, config, docs)",
10
+ "subdomain": "specific subdomain if applicable (e.g., oauth, session, profile)",
11
+ "affectedFiles": ["list of likely affected file paths"],
12
+ "semanticTags": ["relevant keywords and concepts"],
13
+ "estimatedScope": "small | medium | large",
14
+ "dependencies": ["other domains this might depend on or affect"],
15
+ "summary": "brief one-line summary of the intent"
16
+ }
17
+
18
+ Consider:
19
+ - What part of the system does this affect?
20
+ - What files would likely be modified?
21
+ - How complex is this task?
22
+ - What other parts of the system might be affected?`;
23
+
24
+ export interface SmartIntentAnalysis extends IntentAnalysis {
25
+ summary: string;
26
+ confidence: number;
27
+ }
28
+
29
+ export class SmartIntentAnalyzer {
30
+ constructor(private provider: LLMProvider) {}
31
+
32
+ async analyze(prompt: string, projectContext?: string): Promise<SmartIntentAnalysis> {
33
+ const messages: LLMMessage[] = [
34
+ { role: 'system', content: INTENT_ANALYSIS_PROMPT },
35
+ ];
36
+
37
+ if (projectContext) {
38
+ messages.push({
39
+ role: 'user',
40
+ content: `Project context:\n${projectContext}\n\nNow analyze this request:`,
41
+ });
42
+ }
43
+
44
+ messages.push({
45
+ role: 'user',
46
+ content: prompt,
47
+ });
48
+
49
+ try {
50
+ const result = await this.provider.completeJSON<{
51
+ domain: string;
52
+ subdomain?: string;
53
+ affectedFiles: string[];
54
+ semanticTags: string[];
55
+ estimatedScope: 'small' | 'medium' | 'large';
56
+ dependencies: string[];
57
+ summary: string;
58
+ }>(messages, { temperature: 0.3 });
59
+
60
+ return {
61
+ domain: result.domain || 'general',
62
+ subdomain: result.subdomain,
63
+ affectedFiles: result.affectedFiles || [],
64
+ semanticTags: result.semanticTags || [],
65
+ estimatedScope: result.estimatedScope || 'medium',
66
+ dependencies: result.dependencies || [],
67
+ summary: result.summary || prompt.slice(0, 50),
68
+ confidence: 0.9,
69
+ };
70
+ } catch (error) {
71
+ console.error('[SmartIntentAnalyzer] LLM analysis failed, using fallback:', error);
72
+ return this.fallbackAnalysis(prompt);
73
+ }
74
+ }
75
+
76
+ private fallbackAnalysis(prompt: string): SmartIntentAnalysis {
77
+ const lowerPrompt = prompt.toLowerCase();
78
+
79
+ const domainKeywords: Record<string, string[]> = {
80
+ auth: ['login', 'logout', 'auth', 'oauth', 'session', 'token', 'password', '로그인', '인증'],
81
+ user: ['user', 'profile', 'account', '사용자', '회원', '프로필'],
82
+ payment: ['payment', 'pay', 'billing', 'checkout', 'cart', '결제', '장바구니'],
83
+ database: ['database', 'db', 'migration', 'schema', 'model'],
84
+ api: ['api', 'endpoint', 'route', 'controller'],
85
+ ui: ['ui', 'component', 'button', 'form', 'modal', '버튼', '컴포넌트'],
86
+ };
87
+
88
+ let domain = 'general';
89
+ let maxScore = 0;
90
+
91
+ for (const [d, keywords] of Object.entries(domainKeywords)) {
92
+ const score = keywords.filter(kw => lowerPrompt.includes(kw)).length;
93
+ if (score > maxScore) {
94
+ maxScore = score;
95
+ domain = d;
96
+ }
97
+ }
98
+
99
+ return {
100
+ domain,
101
+ subdomain: undefined,
102
+ affectedFiles: [],
103
+ semanticTags: [],
104
+ estimatedScope: 'medium',
105
+ dependencies: [],
106
+ summary: prompt.slice(0, 50),
107
+ confidence: 0.5,
108
+ };
109
+ }
110
+
111
+ async checkOverlap(
112
+ intent1: SmartIntentAnalysis,
113
+ intent2: SmartIntentAnalysis
114
+ ): Promise<{
115
+ hasOverlap: boolean;
116
+ overlapDomains: string[];
117
+ overlapFiles: string[];
118
+ severity: 'none' | 'low' | 'medium' | 'high';
119
+ reason: string;
120
+ }> {
121
+ const messages: LLMMessage[] = [
122
+ {
123
+ role: 'system',
124
+ content: `You analyze whether two development intents might conflict with each other.
125
+ Consider: shared files, shared domains, logical dependencies, potential merge conflicts.
126
+
127
+ Respond in JSON:
128
+ {
129
+ "hasOverlap": boolean,
130
+ "overlapDomains": ["shared domains"],
131
+ "overlapFiles": ["potentially conflicting files"],
132
+ "severity": "none" | "low" | "medium" | "high",
133
+ "reason": "brief explanation"
134
+ }`,
135
+ },
136
+ {
137
+ role: 'user',
138
+ content: `Intent 1:
139
+ - Domain: ${intent1.domain}${intent1.subdomain ? '/' + intent1.subdomain : ''}
140
+ - Summary: ${intent1.summary}
141
+ - Files: ${intent1.affectedFiles.join(', ') || 'unknown'}
142
+ - Tags: ${intent1.semanticTags.join(', ')}
143
+
144
+ Intent 2:
145
+ - Domain: ${intent2.domain}${intent2.subdomain ? '/' + intent2.subdomain : ''}
146
+ - Summary: ${intent2.summary}
147
+ - Files: ${intent2.affectedFiles.join(', ') || 'unknown'}
148
+ - Tags: ${intent2.semanticTags.join(', ')}
149
+
150
+ Do these intents potentially conflict?`,
151
+ },
152
+ ];
153
+
154
+ try {
155
+ return await this.provider.completeJSON(messages, { temperature: 0.2 });
156
+ } catch {
157
+ const sameDomain = intent1.domain === intent2.domain;
158
+ const sharedFiles = intent1.affectedFiles.filter(f => intent2.affectedFiles.includes(f));
159
+
160
+ return {
161
+ hasOverlap: sameDomain || sharedFiles.length > 0,
162
+ overlapDomains: sameDomain ? [intent1.domain] : [],
163
+ overlapFiles: sharedFiles,
164
+ severity: sameDomain ? 'medium' : sharedFiles.length > 0 ? 'low' : 'none',
165
+ reason: sameDomain ? 'Same domain' : sharedFiles.length > 0 ? 'Shared files' : 'No overlap detected',
166
+ };
167
+ }
168
+ }
169
+ }
@@ -0,0 +1,219 @@
1
+ import { WebSocketServer, WebSocket } from 'ws';
2
+ import type {
3
+ AgentMessage,
4
+ HubConnectedMessage,
5
+ BroadcastUserJoinedMessage,
6
+ BroadcastUserLeftMessage,
7
+ } from '@anthropic-for-korea/intent-hub-shared';
8
+ import {
9
+ parseMessage,
10
+ serializeMessage,
11
+ createMessage,
12
+ generateSessionId,
13
+ } from '@anthropic-for-korea/intent-hub-shared';
14
+ import { SessionManager } from '../state/session-manager.js';
15
+ import { LockManager } from '../core/lock-manager.js';
16
+ import { MessageHandler } from './message-handler.js';
17
+
18
+ export interface HubServerOptions {
19
+ port: number;
20
+ host?: string;
21
+ }
22
+
23
+ export interface ConnectedClient {
24
+ socket: WebSocket;
25
+ sessionId: string;
26
+ userId: string;
27
+ username: string;
28
+ projectPath: string;
29
+ connectedAt: number;
30
+ }
31
+
32
+ export class HubServer {
33
+ private wss: WebSocketServer | null = null;
34
+ private clients: Map<string, ConnectedClient> = new Map();
35
+ private sessionManager: SessionManager;
36
+ private lockManager: LockManager;
37
+ private messageHandler: MessageHandler;
38
+
39
+ constructor(private options: HubServerOptions) {
40
+ this.sessionManager = new SessionManager();
41
+ this.lockManager = new LockManager();
42
+ this.messageHandler = new MessageHandler(
43
+ this.sessionManager,
44
+ this.lockManager,
45
+ this.sendToClient.bind(this),
46
+ this.broadcast.bind(this)
47
+ );
48
+ }
49
+
50
+ start(): void {
51
+ this.wss = new WebSocketServer({
52
+ port: this.options.port,
53
+ host: this.options.host ?? '0.0.0.0',
54
+ });
55
+
56
+ this.wss.on('connection', (socket) => {
57
+ this.handleConnection(socket);
58
+ });
59
+
60
+ this.wss.on('error', (error) => {
61
+ console.error('[Hub] Server error:', error);
62
+ });
63
+
64
+ console.log(`[Hub] Server started on ${this.options.host ?? '0.0.0.0'}:${this.options.port}`);
65
+ }
66
+
67
+ stop(): void {
68
+ if (this.wss) {
69
+ for (const client of this.clients.values()) {
70
+ client.socket.close();
71
+ }
72
+ this.clients.clear();
73
+ this.wss.close();
74
+ this.wss = null;
75
+ console.log('[Hub] Server stopped');
76
+ }
77
+ }
78
+
79
+ private handleConnection(socket: WebSocket): void {
80
+ const tempSessionId = generateSessionId();
81
+
82
+ socket.on('message', (data) => {
83
+ const message = parseMessage(data.toString());
84
+ if (!message) {
85
+ console.warn('[Hub] Invalid message received');
86
+ return;
87
+ }
88
+
89
+ this.handleMessage(socket, tempSessionId, message as AgentMessage);
90
+ });
91
+
92
+ socket.on('close', () => {
93
+ this.handleDisconnection(tempSessionId);
94
+ });
95
+
96
+ socket.on('error', (error) => {
97
+ console.error(`[Hub] Socket error for session ${tempSessionId}:`, error);
98
+ });
99
+ }
100
+
101
+ private handleMessage(socket: WebSocket, tempSessionId: string, message: AgentMessage): void {
102
+ if (message.type === 'agent:connect') {
103
+ const sessionId = generateSessionId();
104
+ const client: ConnectedClient = {
105
+ socket,
106
+ sessionId,
107
+ userId: message.payload.userId,
108
+ username: message.payload.username,
109
+ projectPath: message.payload.projectPath,
110
+ connectedAt: Date.now(),
111
+ };
112
+
113
+ this.clients.set(sessionId, client);
114
+ this.sessionManager.addSession(sessionId, client.userId, client.username);
115
+
116
+ const response = createMessage<HubConnectedMessage>('hub:connected', {
117
+ payload: {
118
+ sessionId,
119
+ activeLocks: this.lockManager.getActiveLocks().map(lock => ({
120
+ domainId: lock.domainId,
121
+ username: lock.username,
122
+ })),
123
+ },
124
+ });
125
+ this.sendToClient(sessionId, response);
126
+
127
+ const joinBroadcast = createMessage<BroadcastUserJoinedMessage>('broadcast:user:joined', {
128
+ payload: {
129
+ userId: client.userId,
130
+ username: client.username,
131
+ },
132
+ });
133
+ this.broadcastExcept(sessionId, joinBroadcast);
134
+
135
+ console.log(`[Hub] Client connected: ${client.username} (${sessionId})`);
136
+ return;
137
+ }
138
+
139
+ const client = this.findClientBySocket(socket);
140
+ if (!client) {
141
+ console.warn('[Hub] Message from unknown client');
142
+ return;
143
+ }
144
+
145
+ this.messageHandler.handle(client.sessionId, message);
146
+ }
147
+
148
+ private handleDisconnection(sessionId: string): void {
149
+ const client = this.clients.get(sessionId);
150
+ if (!client) {
151
+ for (const [sid, c] of this.clients) {
152
+ if (c.socket.readyState === WebSocket.CLOSED) {
153
+ this.performDisconnection(sid, c);
154
+ }
155
+ }
156
+ return;
157
+ }
158
+
159
+ this.performDisconnection(sessionId, client);
160
+ }
161
+
162
+ private performDisconnection(sessionId: string, client: ConnectedClient): void {
163
+ this.lockManager.releaseAllForUser(client.userId);
164
+ this.sessionManager.removeSession(sessionId);
165
+ this.clients.delete(sessionId);
166
+
167
+ const leaveBroadcast = createMessage<BroadcastUserLeftMessage>('broadcast:user:left', {
168
+ payload: {
169
+ userId: client.userId,
170
+ username: client.username,
171
+ },
172
+ });
173
+ this.broadcast(leaveBroadcast);
174
+
175
+ console.log(`[Hub] Client disconnected: ${client.username} (${sessionId})`);
176
+ }
177
+
178
+ private findClientBySocket(socket: WebSocket): ConnectedClient | undefined {
179
+ for (const client of this.clients.values()) {
180
+ if (client.socket === socket) {
181
+ return client;
182
+ }
183
+ }
184
+ return undefined;
185
+ }
186
+
187
+ private sendToClient(sessionId: string, message: HubConnectedMessage | BroadcastUserJoinedMessage | BroadcastUserLeftMessage | any): void {
188
+ const client = this.clients.get(sessionId);
189
+ if (client && client.socket.readyState === WebSocket.OPEN) {
190
+ client.socket.send(serializeMessage(message));
191
+ }
192
+ }
193
+
194
+ private broadcast(message: BroadcastUserJoinedMessage | BroadcastUserLeftMessage | any): void {
195
+ const serialized = serializeMessage(message);
196
+ for (const client of this.clients.values()) {
197
+ if (client.socket.readyState === WebSocket.OPEN) {
198
+ client.socket.send(serialized);
199
+ }
200
+ }
201
+ }
202
+
203
+ private broadcastExcept(excludeSessionId: string, message: BroadcastUserJoinedMessage | BroadcastUserLeftMessage | any): void {
204
+ const serialized = serializeMessage(message);
205
+ for (const [sessionId, client] of this.clients) {
206
+ if (sessionId !== excludeSessionId && client.socket.readyState === WebSocket.OPEN) {
207
+ client.socket.send(serialized);
208
+ }
209
+ }
210
+ }
211
+
212
+ getConnectedClients(): ConnectedClient[] {
213
+ return Array.from(this.clients.values());
214
+ }
215
+
216
+ getClientCount(): number {
217
+ return this.clients.size;
218
+ }
219
+ }
@@ -0,0 +1,4 @@
1
+ export { HubServer } from './hub-server.js';
2
+ export { MessageHandler } from './message-handler.js';
3
+ export { SmartHubServer } from './smart-hub-server.js';
4
+ export type { SmartHubServerOptions } from './smart-hub-server.js';
@@ -0,0 +1,111 @@
1
+ import type {
2
+ AgentMessage,
3
+ AgentIntentSubmitMessage,
4
+ AgentDecisionMessage,
5
+ HubIntentReceivedMessage,
6
+ HubLockAcquiredMessage,
7
+ HubLockBlockedMessage,
8
+ BroadcastLockUpdateMessage,
9
+ HubMessage,
10
+ BroadcastMessage,
11
+ } from '@anthropic-for-korea/intent-hub-shared';
12
+ import { createMessage, generateIntentId } from '@anthropic-for-korea/intent-hub-shared';
13
+ import type { SessionManager } from '../state/session-manager.js';
14
+ import type { LockManager } from '../core/lock-manager.js';
15
+ import { IntentAnalyzer } from '../core/intent-analyzer.js';
16
+
17
+ type SendFn = (sessionId: string, message: HubMessage | BroadcastMessage) => void;
18
+ type BroadcastFn = (message: BroadcastMessage) => void;
19
+
20
+ export class MessageHandler {
21
+ private intentAnalyzer: IntentAnalyzer;
22
+
23
+ constructor(
24
+ private sessionManager: SessionManager,
25
+ private lockManager: LockManager,
26
+ private send: SendFn,
27
+ private broadcast: BroadcastFn,
28
+ ) {
29
+ this.intentAnalyzer = new IntentAnalyzer();
30
+ }
31
+
32
+ handle(sessionId: string, message: AgentMessage): void {
33
+ switch (message.type) {
34
+ case 'agent:intent:submit':
35
+ this.handleIntentSubmit(sessionId, message);
36
+ break;
37
+ case 'agent:decision':
38
+ this.handleDecision(sessionId, message);
39
+ break;
40
+ case 'agent:heartbeat':
41
+ break;
42
+ default:
43
+ console.warn(`[MessageHandler] Unknown message type: ${(message as AgentMessage).type}`);
44
+ }
45
+ }
46
+
47
+ private handleIntentSubmit(sessionId: string, message: AgentIntentSubmitMessage): void {
48
+ const session = this.sessionManager.getSession(sessionId);
49
+ if (!session) {
50
+ console.warn(`[MessageHandler] Session not found: ${sessionId}`);
51
+ return;
52
+ }
53
+
54
+ const intentId = generateIntentId();
55
+ const { prompt } = message.payload;
56
+
57
+ const receivedMsg = createMessage<HubIntentReceivedMessage>('hub:intent:received', {
58
+ payload: { intentId },
59
+ });
60
+ this.send(sessionId, receivedMsg);
61
+
62
+ const analysis = this.intentAnalyzer.analyze(prompt);
63
+ const domainId = `${analysis.domain}${analysis.subdomain ? ':' + analysis.subdomain : ''}`;
64
+
65
+ const existingLock = this.lockManager.getLockForDomain(domainId);
66
+
67
+ if (existingLock && existingLock.userId !== session.userId) {
68
+ const blockedMsg = createMessage<HubLockBlockedMessage>('hub:lock:blocked', {
69
+ payload: {
70
+ intentId,
71
+ blockedBy: [{
72
+ domain: domainId,
73
+ username: existingLock.username,
74
+ intentSummary: 'Currently working on this domain',
75
+ }],
76
+ },
77
+ });
78
+ this.send(sessionId, blockedMsg);
79
+ console.log(`[MessageHandler] Intent blocked: ${session.username} blocked by ${existingLock.username} on ${domainId}`);
80
+ return;
81
+ }
82
+
83
+ this.lockManager.acquireLock(domainId, intentId, session.userId, session.username);
84
+
85
+ const acquiredMsg = createMessage<HubLockAcquiredMessage>('hub:lock:acquired', {
86
+ payload: {
87
+ intentId,
88
+ domains: [domainId],
89
+ },
90
+ });
91
+ this.send(sessionId, acquiredMsg);
92
+
93
+ const lockUpdateMsg = createMessage<BroadcastLockUpdateMessage>('broadcast:lock:update', {
94
+ payload: {
95
+ locks: this.lockManager.getActiveLocks().map(lock => ({
96
+ domainId: lock.domainId,
97
+ username: lock.username,
98
+ intentSummary: prompt.slice(0, 50),
99
+ })),
100
+ },
101
+ });
102
+ this.broadcast(lockUpdateMsg);
103
+
104
+ console.log(`[MessageHandler] Lock acquired: ${session.username} on ${domainId}`);
105
+ }
106
+
107
+ private handleDecision(sessionId: string, message: AgentDecisionMessage): void {
108
+ const { conflictId, choice } = message.payload;
109
+ console.log(`[MessageHandler] Decision received for conflict ${conflictId}: ${choice}`);
110
+ }
111
+ }