triflux 3.1.0-dev.5 → 3.2.0-dev.2

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.
package/hub/server.mjs CHANGED
@@ -14,6 +14,12 @@ import { createStore } from './store.mjs';
14
14
  import { createRouter } from './router.mjs';
15
15
  import { createHitlManager } from './hitl.mjs';
16
16
  import { createTools } from './tools.mjs';
17
+ import {
18
+ teamInfo,
19
+ teamTaskList,
20
+ teamTaskUpdate,
21
+ teamSendMessage,
22
+ } from './team/nativeProxy.mjs';
17
23
 
18
24
  /** initialize 요청 판별 */
19
25
  function isInitializeRequest(body) {
@@ -163,6 +169,71 @@ export async function startHub({ port = 27888, dbPath, host = '127.0.0.1' } = {}
163
169
  return res.end(JSON.stringify(result));
164
170
  }
165
171
 
172
+ // POST /bridge/control — 리드 제어를 특정 워커 메일박스로 직접 전달
173
+ if (path === '/bridge/control' && req.method === 'POST') {
174
+ const {
175
+ from_agent = 'lead',
176
+ to_agent,
177
+ command,
178
+ reason = '',
179
+ payload = {},
180
+ trace_id,
181
+ correlation_id,
182
+ ttl_ms = 3600000,
183
+ } = body;
184
+
185
+ if (!to_agent || !command) {
186
+ res.writeHead(400);
187
+ return res.end(JSON.stringify({ ok: false, error: 'to_agent, command 필수' }));
188
+ }
189
+
190
+ const result = router.handlePublish({
191
+ from: from_agent,
192
+ to: to_agent,
193
+ topic: 'lead.control',
194
+ payload: {
195
+ command,
196
+ reason,
197
+ ...payload,
198
+ issued_at: Date.now(),
199
+ },
200
+ priority: 8,
201
+ ttl_ms: Math.max(1000, Math.min(Number(ttl_ms) || 3600000, 86400000)),
202
+ trace_id,
203
+ correlation_id,
204
+ });
205
+
206
+ res.writeHead(200);
207
+ return res.end(JSON.stringify(result));
208
+ }
209
+
210
+ // POST /bridge/team/* — Native Teams 파일 프록시
211
+ if (req.method === 'POST') {
212
+ let teamResult = null;
213
+ if (path === '/bridge/team/info' || path === '/bridge/team-info') {
214
+ teamResult = teamInfo(body);
215
+ } else if (path === '/bridge/team/task-list' || path === '/bridge/team-task-list') {
216
+ teamResult = teamTaskList(body);
217
+ } else if (path === '/bridge/team/task-update' || path === '/bridge/team-task-update') {
218
+ teamResult = teamTaskUpdate(body);
219
+ } else if (path === '/bridge/team/send-message' || path === '/bridge/team-send-message') {
220
+ teamResult = teamSendMessage(body);
221
+ }
222
+
223
+ if (teamResult) {
224
+ let status = 200;
225
+ const code = teamResult?.error?.code;
226
+ if (!teamResult.ok) {
227
+ if (code === 'TEAM_NOT_FOUND' || code === 'TASK_NOT_FOUND' || code === 'TASKS_DIR_NOT_FOUND') status = 404;
228
+ else if (code === 'CLAIM_CONFLICT' || code === 'MTIME_CONFLICT') status = 409;
229
+ else if (code === 'INVALID_TEAM_NAME' || code === 'INVALID_TASK_ID' || code === 'INVALID_TEXT' || code === 'INVALID_FROM') status = 400;
230
+ else status = 500;
231
+ }
232
+ res.writeHead(status);
233
+ return res.end(JSON.stringify(teamResult));
234
+ }
235
+ }
236
+
166
237
  // POST /bridge/context — 선행 컨텍스트 폴링
167
238
  if (path === '/bridge/context' && req.method === 'POST') {
168
239
  const { agent_id, topics, max_messages = 10 } = body;
@@ -214,23 +285,23 @@ export async function startHub({ port = 27888, dbPath, host = '127.0.0.1' } = {}
214
285
  if (req.method === 'POST') {
215
286
  const body = await parseBody(req);
216
287
 
217
- if (sessionId && transports.has(sessionId)) {
218
- // 기존 세션
219
- const t = transports.get(sessionId);
220
- t._lastActivity = Date.now();
221
- await t.handleRequest(req, res, body);
222
- } else if (!sessionId && isInitializeRequest(body)) {
223
- // 새 세션 초기화
224
- const transport = new StreamableHTTPServerTransport({
225
- sessionIdGenerator: () => randomUUID(),
226
- onsessioninitialized: (sid) => {
227
- transport._lastActivity = Date.now();
228
- transports.set(sid, transport);
229
- },
230
- });
231
- transport.onclose = () => {
232
- if (transport.sessionId) transports.delete(transport.sessionId);
233
- };
288
+ if (sessionId && transports.has(sessionId)) {
289
+ // 기존 세션
290
+ const t = transports.get(sessionId);
291
+ t._lastActivity = Date.now();
292
+ await t.handleRequest(req, res, body);
293
+ } else if (!sessionId && isInitializeRequest(body)) {
294
+ // 새 세션 초기화
295
+ const transport = new StreamableHTTPServerTransport({
296
+ sessionIdGenerator: () => randomUUID(),
297
+ onsessioninitialized: (sid) => {
298
+ transport._lastActivity = Date.now();
299
+ transports.set(sid, transport);
300
+ },
301
+ });
302
+ transport.onclose = () => {
303
+ if (transport.sessionId) transports.delete(transport.sessionId);
304
+ };
234
305
  const mcp = createMcpForSession();
235
306
  await mcp.connect(transport);
236
307
  await transport.handleRequest(req, res, body);
@@ -288,18 +359,18 @@ export async function startHub({ port = 27888, dbPath, host = '127.0.0.1' } = {}
288
359
  }, 10000);
289
360
  hitlTimer.unref();
290
361
 
291
- // 비활성 세션 정리 (60초 주기, 30분 TTL)
292
- const SESSION_TTL_MS = 30 * 60 * 1000;
293
- const sessionTimer = setInterval(() => {
294
- const now = Date.now();
295
- for (const [sid, transport] of transports) {
296
- if (now - (transport._lastActivity || 0) > SESSION_TTL_MS) {
297
- try { transport.close(); } catch {}
298
- transports.delete(sid);
299
- }
300
- }
301
- }, 60000);
302
- sessionTimer.unref();
362
+ // 비활성 세션 정리 (60초 주기, 30분 TTL)
363
+ const SESSION_TTL_MS = 30 * 60 * 1000;
364
+ const sessionTimer = setInterval(() => {
365
+ const now = Date.now();
366
+ for (const [sid, transport] of transports) {
367
+ if (now - (transport._lastActivity || 0) > SESSION_TTL_MS) {
368
+ try { transport.close(); } catch {}
369
+ transports.delete(sid);
370
+ }
371
+ }
372
+ }, 60000);
373
+ sessionTimer.unref();
303
374
 
304
375
  // PID 파일 기록
305
376
  mkdirSync(PID_DIR, { recursive: true });