triflux 3.2.0-dev.1 → 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/bin/triflux.mjs +185 -43
- package/hooks/hooks.json +12 -0
- package/hub/bridge.mjs +137 -51
- package/hub/server.mjs +100 -29
- package/hub/team/cli.mjs +1080 -113
- package/hub/team/native-supervisor.mjs +300 -0
- package/hub/team/native.mjs +92 -0
- package/hub/team/nativeProxy.mjs +455 -0
- package/hub/team/orchestrator.mjs +99 -35
- package/hub/team/pane.mjs +18 -9
- package/hub/team/session.mjs +359 -16
- package/hub/tools.mjs +113 -15
- package/package.json +1 -1
- package/scripts/setup.mjs +95 -0
- package/scripts/team-keyword.mjs +35 -0
- package/scripts/tfx-route.sh +13 -73
- package/skills/tfx-team/SKILL.md +204 -86
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 });
|