@triflux/remote 10.1.0 → 10.26.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.
- package/hub/pipe.mjs +239 -121
- package/hub/public/dashboard.html +61 -0
- package/hub/server.mjs +2179 -709
- package/hub/store-adapter.mjs +59 -26
- package/hub/store.mjs +356 -153
- package/hub/team/agent-map.json +26 -7
- package/hub/team/ansi.mjs +245 -146
- package/hub/team/backend.mjs +78 -17
- package/hub/team/build-worker-prompt.mjs +73 -0
- package/hub/team/check-mcp-hub.mjs +49 -0
- package/hub/team/claude-daemon-control.mjs +1130 -0
- package/hub/team/claude-native-bridge.mjs +1011 -0
- package/hub/team/claude-session-projection.mjs +74 -0
- package/hub/team/cli/commands/attach.mjs +57 -37
- package/hub/team/cli/commands/control.mjs +29 -8
- package/hub/team/cli/commands/debug.mjs +95 -74
- package/hub/team/cli/commands/focus.mjs +80 -53
- package/hub/team/cli/commands/interrupt.mjs +18 -6
- package/hub/team/cli/commands/kill.mjs +16 -5
- package/hub/team/cli/commands/list.mjs +31 -24
- package/hub/team/cli/commands/send.mjs +19 -6
- package/hub/team/cli/commands/start/index.mjs +151 -33
- package/hub/team/cli/commands/start/parse-args.mjs +97 -13
- package/hub/team/cli/commands/start/start-headless.mjs +155 -37
- package/hub/team/cli/commands/start/start-in-process.mjs +50 -40
- package/hub/team/cli/commands/start/start-mux.mjs +122 -73
- package/hub/team/cli/commands/start/start-wt.mjs +86 -69
- package/hub/team/cli/commands/status.mjs +43 -14
- package/hub/team/cli/commands/stop.mjs +11 -4
- package/hub/team/cli/commands/task.mjs +8 -3
- package/hub/team/cli/commands/tasks.mjs +13 -13
- package/hub/team/cli/help.mjs +1 -1
- package/hub/team/cli/index.mjs +2 -2
- package/hub/team/cli/manifest.mjs +38 -8
- package/hub/team/cli/render.mjs +52 -30
- package/hub/team/cli/services/attach-fallback.mjs +74 -54
- package/hub/team/cli/services/hub-client.mjs +44 -16
- package/hub/team/cli/services/member-selector.mjs +37 -30
- package/hub/team/cli/services/native-control.mjs +144 -116
- package/hub/team/cli/services/runtime-mode.mjs +61 -4
- package/hub/team/cli/services/state-store.mjs +66 -9
- package/hub/team/cli/services/task-model.mjs +40 -30
- package/hub/team/codex-review.mjs +521 -0
- package/hub/team/conductor-mesh-bridge.mjs +25 -24
- package/hub/team/conductor-registry.mjs +121 -0
- package/hub/team/conductor.mjs +1379 -671
- package/hub/team/consensus-meta.mjs +176 -0
- package/hub/team/dashboard-anchor.mjs +4 -5
- package/hub/team/dashboard-layout.mjs +3 -1
- package/hub/team/dashboard-open.mjs +20 -118
- package/hub/team/dashboard.mjs +76 -28
- package/hub/team/event-log.mjs +18 -10
- package/hub/team/execution-mode.mjs +256 -0
- package/hub/team/extract-completion-payload.mjs +110 -0
- package/hub/team/git-preflight.mjs +286 -0
- package/hub/team/handoff.mjs +49 -18
- package/hub/team/headless-bridge-session.mjs +17 -0
- package/hub/team/headless.mjs +2213 -1156
- package/hub/team/health-probe.mjs +232 -69
- package/hub/team/interactive-native-launcher.mjs +106 -0
- package/hub/team/interactive-native-worker.mjs +80 -0
- package/hub/team/interactive-tui-transport.mjs +465 -0
- package/hub/team/launcher-template.mjs +49 -17
- package/hub/team/lead-control.mjs +91 -4
- package/hub/team/mcp-selector.mjs +145 -0
- package/hub/team/native-supervisor.mjs +90 -26
- package/hub/team/native.mjs +120 -42
- package/hub/team/nativeProxy.mjs +184 -113
- package/hub/team/notify.mjs +147 -78
- package/hub/team/orchestrator.mjs +173 -161
- package/hub/team/pane.mjs +43 -20
- package/hub/team/process-cleanup.mjs +69 -25
- package/hub/team/psmux.mjs +1022 -281
- package/hub/team/recovery-store.mjs +149 -0
- package/hub/team/remote-probe.mjs +68 -52
- package/hub/team/remote-session.mjs +131 -59
- package/hub/team/remote-watcher.mjs +61 -33
- package/hub/team/retry-state-machine.mjs +336 -0
- package/hub/team/routing.mjs +51 -25
- package/hub/team/runtime-strategy.mjs +200 -0
- package/hub/team/schemas/worker-completion.json +39 -0
- package/hub/team/sentinel-capture.mjs +127 -0
- package/hub/team/session-sync.mjs +153 -6
- package/hub/team/session.mjs +574 -611
- package/hub/team/shared.mjs +13 -13
- package/hub/team/staleState.mjs +72 -30
- package/hub/team/swarm-cli.mjs +390 -0
- package/hub/team/swarm-hypervisor.mjs +1544 -193
- package/hub/team/swarm-intent.mjs +216 -0
- package/hub/team/swarm-locks.mjs +140 -25
- package/hub/team/swarm-planner.mjs +121 -21
- package/hub/team/swarm-preflight.mjs +319 -0
- package/hub/team/swarm-reconciler.mjs +128 -23
- package/hub/team/synapse-cli.mjs +458 -0
- package/hub/team/synapse-http.mjs +60 -0
- package/hub/team/synapse-registry.mjs +329 -0
- package/hub/team/terminal-opener.mjs +216 -0
- package/hub/team/tui-core.mjs +292 -0
- package/hub/team/tui-lite.mjs +237 -148
- package/hub/team/tui-remote-adapter.mjs +18 -34
- package/hub/team/tui-synapse.mjs +223 -0
- package/hub/team/tui-viewer.mjs +108 -48
- package/hub/team/tui-widgets.mjs +262 -0
- package/hub/team/tui.mjs +931 -439
- package/hub/team/worker-completion-validator.mjs +84 -0
- package/hub/team/worker-sandbox.mjs +97 -0
- package/hub/team/worker-signal.mjs +263 -0
- package/hub/team/worker-signal.types.d.ts +52 -0
- package/hub/team/worktree-lifecycle.mjs +517 -63
- package/hub/team/wt-manager.mjs +297 -26
- package/hub/tools.mjs +477 -252
- package/hub/tray.mjs +411 -368
- package/hub/workers/antigravity-route-worker.mjs +169 -0
- package/hub/workers/claude-worker.mjs +162 -118
- package/hub/workers/codex-app-server-worker.mjs +1015 -0
- package/hub/workers/codex-mcp.mjs +724 -507
- package/hub/workers/delegator-mcp.mjs +570 -499
- package/hub/workers/factory.mjs +103 -21
- package/hub/workers/gemini-worker.mjs +126 -284
- package/hub/workers/interface.mjs +24 -2
- package/hub/workers/lib/jsonrpc-stdio.mjs +463 -0
- package/hub/workers/worker-utils.mjs +23 -17
- package/package.json +4 -3
- package/scripts/lib/agent-json.mjs +27 -0
- package/scripts/lib/async.mjs +174 -0
- package/scripts/lib/claudemd-scanner.mjs +223 -0
- package/scripts/lib/cli-agy.mjs +62 -0
- package/scripts/lib/cli-claude.mjs +78 -0
- package/scripts/lib/cli-codex.mjs +199 -0
- package/scripts/lib/cli-gemini.mjs +37 -0
- package/scripts/lib/codex-recovery.sh +50 -0
- package/scripts/lib/context.mjs +67 -0
- package/scripts/lib/cross-review-utils.mjs +59 -0
- package/scripts/lib/doctor-env-checks.mjs +121 -0
- package/scripts/lib/dynamic-route-cli.mjs +107 -0
- package/scripts/lib/env-probe.mjs +269 -0
- package/scripts/lib/env.mjs +14 -0
- package/scripts/lib/gemini-profiles.mjs +119 -0
- package/scripts/lib/handoff.mjs +187 -0
- package/scripts/lib/hook-utils.mjs +16 -0
- package/scripts/lib/hub.mjs +48 -0
- package/scripts/lib/keyword-rules.mjs +195 -0
- package/scripts/lib/logger.mjs +105 -0
- package/scripts/lib/mcp-filter.mjs +883 -0
- package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
- package/scripts/lib/mcp-gateway-servers.mjs +70 -0
- package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
- package/scripts/lib/mcp-guard-engine.mjs +2120 -0
- package/scripts/lib/mcp-health.mjs +773 -0
- package/scripts/lib/mcp-manifest.mjs +89 -0
- package/scripts/lib/mcp-server-catalog.mjs +355 -0
- package/scripts/lib/pid.mjs +63 -0
- package/scripts/lib/process-utils.mjs +26 -0
- package/scripts/lib/psmux-info.mjs +224 -0
- package/scripts/lib/quota.mjs +47 -0
- package/scripts/lib/remote-spawn-transfer.mjs +226 -0
- package/scripts/lib/skill-state.mjs +220 -0
- package/scripts/lib/skill-template.mjs +375 -0
- package/scripts/lib/team.mjs +75 -0
- package/scripts/lib/timeout.mjs +66 -0
- package/scripts/lib/tmp.mjs +26 -0
- package/scripts/lib/toml.mjs +113 -0
package/hub/pipe.mjs
CHANGED
|
@@ -1,38 +1,37 @@
|
|
|
1
1
|
// hub/pipe.mjs — Named Pipe/Unix socket 제어 채널
|
|
2
2
|
// NDJSON 프로토콜로 에이전트 실시간 제어/이벤트 푸시를 처리한다.
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import { existsSync, unlinkSync } from
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import { createPipeline } from '@triflux/core/hub/pipeline/index.mjs';
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
6
|
+
import net from "node:net";
|
|
7
|
+
import { createPipeline } from "@triflux/core/hub/pipeline/index.mjs";
|
|
9
8
|
import {
|
|
10
9
|
ensurePipelineTable,
|
|
11
10
|
initPipelineState,
|
|
12
11
|
listPipelineStates,
|
|
13
12
|
readPipelineState,
|
|
14
|
-
} from
|
|
15
|
-
import { IS_WINDOWS, pipePath } from
|
|
16
|
-
import {
|
|
13
|
+
} from "@triflux/core/hub/pipeline/state.mjs";
|
|
14
|
+
import { IS_WINDOWS, pipePath } from "@triflux/core/hub/platform.mjs";
|
|
15
|
+
import { sendInputToConductorSession } from "./team/conductor-registry.mjs";
|
|
16
|
+
import { getTeamBridge } from "@triflux/core/hub/team-bridge.mjs";
|
|
17
|
+
import { safeJsonParse } from "./workers/worker-utils.mjs";
|
|
17
18
|
|
|
18
19
|
const DEFAULT_HEARTBEAT_TTL_MS = 60000;
|
|
19
|
-
const TEAM_BRIDGE_NOT_REGISTERED =
|
|
20
|
+
const TEAM_BRIDGE_NOT_REGISTERED = "bridge_not_registered";
|
|
20
21
|
|
|
21
22
|
/** 플랫폼별 pipe 경로 계산 */
|
|
22
23
|
export function getPipePath(sessionId = process.pid) {
|
|
23
|
-
return pipePath(
|
|
24
|
+
return pipePath("triflux", sessionId);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
function normalizeTopics(topics) {
|
|
27
28
|
if (!Array.isArray(topics)) return [];
|
|
28
|
-
return topics
|
|
29
|
-
.map((topic) => String(topic || '').trim())
|
|
30
|
-
.filter(Boolean);
|
|
29
|
+
return topics.map((topic) => String(topic || "").trim()).filter(Boolean);
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
function getTeamBridgeMethod(methodName) {
|
|
34
33
|
const method = getTeamBridge()?.[methodName];
|
|
35
|
-
return typeof method ===
|
|
34
|
+
return typeof method === "function" ? method : null;
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
function teamInfoFallback(payload = {}) {
|
|
@@ -101,7 +100,7 @@ function teamSendMessageFallback(payload = {}) {
|
|
|
101
100
|
ok: true,
|
|
102
101
|
data: {
|
|
103
102
|
message_id: null,
|
|
104
|
-
recipient: payload.to ??
|
|
103
|
+
recipient: payload.to ?? "team-lead",
|
|
105
104
|
inbox_file: null,
|
|
106
105
|
queued_at: null,
|
|
107
106
|
unread_count: 0,
|
|
@@ -125,9 +124,11 @@ export function createPipeServer({
|
|
|
125
124
|
sessionId = process.pid,
|
|
126
125
|
heartbeatTtlMs = DEFAULT_HEARTBEAT_TTL_MS,
|
|
127
126
|
delegatorService = null,
|
|
127
|
+
hitlManager = null,
|
|
128
|
+
onActivity = null,
|
|
128
129
|
} = {}) {
|
|
129
130
|
if (!router) {
|
|
130
|
-
throw new Error(
|
|
131
|
+
throw new Error("router is required");
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
const pipePath = getPipePath(sessionId);
|
|
@@ -146,24 +147,31 @@ export function createPipeServer({
|
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
function sendResponse(client, requestId, result) {
|
|
149
|
-
return sendFrame(client, {
|
|
150
|
+
return sendFrame(client, {
|
|
151
|
+
type: "response",
|
|
152
|
+
request_id: requestId,
|
|
153
|
+
...result,
|
|
154
|
+
});
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
function closeClient(client) {
|
|
153
158
|
if (!client || client.closed) return;
|
|
154
159
|
client.closed = true;
|
|
155
160
|
clients.delete(client.id);
|
|
156
|
-
try {
|
|
161
|
+
try {
|
|
162
|
+
client.socket.destroy();
|
|
163
|
+
} catch {}
|
|
157
164
|
}
|
|
158
165
|
|
|
159
166
|
function touchClient(client) {
|
|
160
167
|
client.lastHeartbeatMs = Date.now();
|
|
168
|
+
if (onActivity) onActivity();
|
|
161
169
|
}
|
|
162
170
|
|
|
163
171
|
function resolveAgentId(client, payload) {
|
|
164
172
|
const agentId = payload?.agent_id || client?.agentId;
|
|
165
173
|
if (!agentId) {
|
|
166
|
-
throw new Error(
|
|
174
|
+
throw new Error("agent_id required");
|
|
167
175
|
}
|
|
168
176
|
return agentId;
|
|
169
177
|
}
|
|
@@ -172,11 +180,13 @@ export function createPipeServer({
|
|
|
172
180
|
let delivered = false;
|
|
173
181
|
for (const client of clients.values()) {
|
|
174
182
|
if (client.agentId !== agentId) continue;
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
183
|
+
if (
|
|
184
|
+
sendFrame(client, {
|
|
185
|
+
type: "event",
|
|
186
|
+
event: "message",
|
|
187
|
+
payload: { agent_id: agentId, message },
|
|
188
|
+
})
|
|
189
|
+
) {
|
|
180
190
|
delivered = true;
|
|
181
191
|
}
|
|
182
192
|
}
|
|
@@ -199,18 +209,20 @@ export function createPipeServer({
|
|
|
199
209
|
|
|
200
210
|
async function processCommand(client, action, payload = {}) {
|
|
201
211
|
switch (action) {
|
|
202
|
-
case
|
|
212
|
+
case "register": {
|
|
203
213
|
const result = router.registerAgent(payload);
|
|
204
214
|
if (client) {
|
|
205
215
|
client.agentId = payload.agent_id;
|
|
206
|
-
client.subscriptions = new Set(
|
|
216
|
+
client.subscriptions = new Set(
|
|
217
|
+
router.getSubscribedTopics(client.agentId),
|
|
218
|
+
);
|
|
207
219
|
touchClient(client);
|
|
208
220
|
pushPendingMessages(client.agentId);
|
|
209
221
|
}
|
|
210
222
|
return { ok: true, data: { ...result, pipe_path: pipePath } };
|
|
211
223
|
}
|
|
212
224
|
|
|
213
|
-
case
|
|
225
|
+
case "subscribe": {
|
|
214
226
|
const agentId = resolveAgentId(client, payload);
|
|
215
227
|
const topics = normalizeTopics(payload.topics);
|
|
216
228
|
const result = router.subscribeAgent(agentId, topics, {
|
|
@@ -228,55 +240,61 @@ export function createPipeServer({
|
|
|
228
240
|
};
|
|
229
241
|
}
|
|
230
242
|
|
|
231
|
-
case
|
|
243
|
+
case "ack": {
|
|
232
244
|
const agentId = resolveAgentId(client, payload);
|
|
233
|
-
const acked = router.ackMessages(
|
|
245
|
+
const acked = router.ackMessages(
|
|
246
|
+
payload.message_ids || payload.ack_ids || [],
|
|
247
|
+
agentId,
|
|
248
|
+
);
|
|
234
249
|
if (client) touchClient(client);
|
|
235
250
|
return { ok: true, data: { agent_id: agentId, acked_count: acked } };
|
|
236
251
|
}
|
|
237
252
|
|
|
238
|
-
case
|
|
253
|
+
case "heartbeat": {
|
|
239
254
|
const agentId = resolveAgentId(client, payload);
|
|
240
|
-
const result = router.refreshAgentLease(
|
|
255
|
+
const result = router.refreshAgentLease(
|
|
256
|
+
agentId,
|
|
257
|
+
payload.heartbeat_ttl_ms || heartbeatTtlMs,
|
|
258
|
+
);
|
|
241
259
|
if (client) touchClient(client);
|
|
242
260
|
return { ok: true, data: result };
|
|
243
261
|
}
|
|
244
262
|
|
|
245
|
-
case
|
|
263
|
+
case "publish": {
|
|
246
264
|
const result = router.handlePublish(payload);
|
|
247
265
|
if (client) touchClient(client);
|
|
248
266
|
return result;
|
|
249
267
|
}
|
|
250
268
|
|
|
251
|
-
case
|
|
269
|
+
case "handoff": {
|
|
252
270
|
const result = router.handleHandoff(payload);
|
|
253
271
|
if (client) touchClient(client);
|
|
254
272
|
return result;
|
|
255
273
|
}
|
|
256
274
|
|
|
257
|
-
case
|
|
275
|
+
case "assign": {
|
|
258
276
|
const result = router.assignAsync(payload);
|
|
259
277
|
if (client) touchClient(client);
|
|
260
278
|
return result;
|
|
261
279
|
}
|
|
262
280
|
|
|
263
|
-
case
|
|
281
|
+
case "assign_result": {
|
|
264
282
|
const result = router.reportAssignResult(payload);
|
|
265
283
|
if (client) touchClient(client);
|
|
266
284
|
return result;
|
|
267
285
|
}
|
|
268
286
|
|
|
269
|
-
case
|
|
287
|
+
case "assign_retry": {
|
|
270
288
|
const result = router.retryAssign(payload.job_id, payload);
|
|
271
289
|
if (client) touchClient(client);
|
|
272
290
|
return result;
|
|
273
291
|
}
|
|
274
292
|
|
|
275
|
-
case
|
|
293
|
+
case "result": {
|
|
276
294
|
const result = router.handlePublish({
|
|
277
295
|
from: payload.agent_id,
|
|
278
|
-
to: `topic:${payload.topic ||
|
|
279
|
-
topic: payload.topic ||
|
|
296
|
+
to: `topic:${payload.topic || "task.result"}`,
|
|
297
|
+
topic: payload.topic || "task.result",
|
|
280
298
|
payload: payload.payload || {},
|
|
281
299
|
priority: 5,
|
|
282
300
|
ttl_ms: 3600000,
|
|
@@ -287,19 +305,22 @@ export function createPipeServer({
|
|
|
287
305
|
return result;
|
|
288
306
|
}
|
|
289
307
|
|
|
290
|
-
case
|
|
308
|
+
case "control": {
|
|
291
309
|
const result = router.handlePublish({
|
|
292
|
-
from: payload.from_agent ||
|
|
310
|
+
from: payload.from_agent || "lead",
|
|
293
311
|
to: payload.to_agent,
|
|
294
|
-
topic:
|
|
312
|
+
topic: "lead.control",
|
|
295
313
|
payload: {
|
|
296
314
|
command: payload.command,
|
|
297
|
-
reason: payload.reason ||
|
|
315
|
+
reason: payload.reason || "",
|
|
298
316
|
...(payload.payload || {}),
|
|
299
317
|
issued_at: Date.now(),
|
|
300
318
|
},
|
|
301
319
|
priority: 8,
|
|
302
|
-
ttl_ms: Math.max(
|
|
320
|
+
ttl_ms: Math.max(
|
|
321
|
+
1000,
|
|
322
|
+
Math.min(Number(payload.ttl_ms) || 3600000, 86400000),
|
|
323
|
+
),
|
|
303
324
|
trace_id: payload.trace_id,
|
|
304
325
|
correlation_id: payload.correlation_id,
|
|
305
326
|
});
|
|
@@ -307,18 +328,23 @@ export function createPipeServer({
|
|
|
307
328
|
return result;
|
|
308
329
|
}
|
|
309
330
|
|
|
310
|
-
case
|
|
331
|
+
case "deregister": {
|
|
311
332
|
const agentId = resolveAgentId(client, payload);
|
|
312
|
-
router.updateAgentStatus(agentId,
|
|
333
|
+
router.updateAgentStatus(agentId, "offline");
|
|
313
334
|
if (client) touchClient(client);
|
|
314
335
|
return {
|
|
315
336
|
ok: true,
|
|
316
|
-
data: { agent_id: agentId, status:
|
|
337
|
+
data: { agent_id: agentId, status: "offline" },
|
|
317
338
|
};
|
|
318
339
|
}
|
|
319
340
|
|
|
320
|
-
case
|
|
321
|
-
|
|
341
|
+
case "send_input": {
|
|
342
|
+
if (client) touchClient(client);
|
|
343
|
+
return sendInputToConductorSession(payload.session_id, payload.text);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
case "team_task_update": {
|
|
347
|
+
const teamTaskUpdate = getTeamBridgeMethod("teamTaskUpdate");
|
|
322
348
|
const result = teamTaskUpdate
|
|
323
349
|
? await teamTaskUpdate(payload)
|
|
324
350
|
: teamTaskUpdateFallback(payload);
|
|
@@ -326,8 +352,8 @@ export function createPipeServer({
|
|
|
326
352
|
return result;
|
|
327
353
|
}
|
|
328
354
|
|
|
329
|
-
case
|
|
330
|
-
const teamSendMessage = getTeamBridgeMethod(
|
|
355
|
+
case "team_send_message": {
|
|
356
|
+
const teamSendMessage = getTeamBridgeMethod("teamSendMessage");
|
|
331
357
|
const result = teamSendMessage
|
|
332
358
|
? await teamSendMessage(payload)
|
|
333
359
|
: teamSendMessageFallback(payload);
|
|
@@ -335,20 +361,20 @@ export function createPipeServer({
|
|
|
335
361
|
return result;
|
|
336
362
|
}
|
|
337
363
|
|
|
338
|
-
case
|
|
364
|
+
case "pipeline_advance": {
|
|
339
365
|
if (client) touchClient(client);
|
|
340
366
|
if (!store?.db) {
|
|
341
|
-
return { ok: false, error:
|
|
367
|
+
return { ok: false, error: "hub_db_not_found" };
|
|
342
368
|
}
|
|
343
369
|
ensurePipelineTable(store.db);
|
|
344
370
|
const pipeline = createPipeline(store.db, payload.team_name);
|
|
345
371
|
return pipeline.advance(payload.phase);
|
|
346
372
|
}
|
|
347
373
|
|
|
348
|
-
case
|
|
374
|
+
case "pipeline_init": {
|
|
349
375
|
if (client) touchClient(client);
|
|
350
376
|
if (!store?.db) {
|
|
351
|
-
return { ok: false, error:
|
|
377
|
+
return { ok: false, error: "hub_db_not_found" };
|
|
352
378
|
}
|
|
353
379
|
ensurePipelineTable(store.db);
|
|
354
380
|
const state = initPipelineState(store.db, payload.team_name, {
|
|
@@ -358,18 +384,46 @@ export function createPipeServer({
|
|
|
358
384
|
return { ok: true, data: state };
|
|
359
385
|
}
|
|
360
386
|
|
|
361
|
-
case
|
|
387
|
+
case "hitl_request": {
|
|
388
|
+
if (!hitlManager) {
|
|
389
|
+
return { ok: false, error: "hitl not available" };
|
|
390
|
+
}
|
|
391
|
+
if (client) touchClient(client);
|
|
392
|
+
return hitlManager.requestHumanInput(payload);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
case "hitl_submit": {
|
|
396
|
+
if (!hitlManager) {
|
|
397
|
+
return { ok: false, error: "hitl not available" };
|
|
398
|
+
}
|
|
399
|
+
if (client) touchClient(client);
|
|
400
|
+
return hitlManager.submitHumanInput(payload);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
case "delegator_delegate": {
|
|
362
404
|
if (!delegatorService) {
|
|
363
|
-
return {
|
|
405
|
+
return {
|
|
406
|
+
ok: false,
|
|
407
|
+
error: {
|
|
408
|
+
code: "DELEGATOR_NOT_AVAILABLE",
|
|
409
|
+
message: "Delegator service가 초기화되지 않았습니다",
|
|
410
|
+
},
|
|
411
|
+
};
|
|
364
412
|
}
|
|
365
413
|
if (client) touchClient(client);
|
|
366
414
|
const result = await delegatorService.delegate(payload);
|
|
367
415
|
return { ok: result?.ok !== false, data: result };
|
|
368
416
|
}
|
|
369
417
|
|
|
370
|
-
case
|
|
418
|
+
case "delegator_reply": {
|
|
371
419
|
if (!delegatorService) {
|
|
372
|
-
return {
|
|
420
|
+
return {
|
|
421
|
+
ok: false,
|
|
422
|
+
error: {
|
|
423
|
+
code: "DELEGATOR_NOT_AVAILABLE",
|
|
424
|
+
message: "Delegator service가 초기화되지 않았습니다",
|
|
425
|
+
},
|
|
426
|
+
};
|
|
373
427
|
}
|
|
374
428
|
if (client) touchClient(client);
|
|
375
429
|
const result = await delegatorService.reply(payload);
|
|
@@ -379,13 +433,19 @@ export function createPipeServer({
|
|
|
379
433
|
default:
|
|
380
434
|
return {
|
|
381
435
|
ok: false,
|
|
382
|
-
error: {
|
|
436
|
+
error: {
|
|
437
|
+
code: "UNKNOWN_PIPE_COMMAND",
|
|
438
|
+
message: `지원하지 않는 command: ${action}`,
|
|
439
|
+
},
|
|
383
440
|
};
|
|
384
441
|
}
|
|
385
442
|
}
|
|
386
443
|
|
|
387
444
|
function buildReplayMessages(agentId, payload = {}) {
|
|
388
|
-
const maxMessages = Math.max(
|
|
445
|
+
const maxMessages = Math.max(
|
|
446
|
+
1,
|
|
447
|
+
Math.min(Number(payload.max_messages) || 20, 100),
|
|
448
|
+
);
|
|
389
449
|
const pending = router.getPendingMessages(agentId, {
|
|
390
450
|
max_messages: maxMessages,
|
|
391
451
|
include_topics: payload.topics,
|
|
@@ -410,7 +470,7 @@ export function createPipeServer({
|
|
|
410
470
|
|
|
411
471
|
async function processQuery(client, action, payload = {}) {
|
|
412
472
|
switch (action) {
|
|
413
|
-
case
|
|
473
|
+
case "drain": {
|
|
414
474
|
const agentId = resolveAgentId(client, payload);
|
|
415
475
|
const messages = router.drainAgent(agentId, {
|
|
416
476
|
max_messages: payload.max_messages,
|
|
@@ -420,69 +480,95 @@ export function createPipeServer({
|
|
|
420
480
|
if (client) touchClient(client);
|
|
421
481
|
return {
|
|
422
482
|
ok: true,
|
|
423
|
-
data: {
|
|
483
|
+
data: {
|
|
484
|
+
messages,
|
|
485
|
+
count: messages.length,
|
|
486
|
+
server_time_ms: Date.now(),
|
|
487
|
+
},
|
|
424
488
|
};
|
|
425
489
|
}
|
|
426
490
|
|
|
427
|
-
case
|
|
491
|
+
case "context": {
|
|
428
492
|
const agentId = resolveAgentId(client, payload);
|
|
429
493
|
const messages = buildReplayMessages(agentId, payload);
|
|
430
494
|
if (client) touchClient(client);
|
|
431
495
|
return {
|
|
432
496
|
ok: true,
|
|
433
|
-
data: {
|
|
497
|
+
data: {
|
|
498
|
+
messages,
|
|
499
|
+
count: messages.length,
|
|
500
|
+
server_time_ms: Date.now(),
|
|
501
|
+
},
|
|
434
502
|
};
|
|
435
503
|
}
|
|
436
504
|
|
|
437
|
-
case
|
|
438
|
-
const scope = payload.scope ||
|
|
505
|
+
case "status": {
|
|
506
|
+
const scope = payload.scope || "hub";
|
|
439
507
|
if (client) touchClient(client);
|
|
440
508
|
return router.getStatus(scope, payload);
|
|
441
509
|
}
|
|
442
510
|
|
|
443
|
-
case
|
|
511
|
+
case "assign_status": {
|
|
444
512
|
if (client) touchClient(client);
|
|
445
513
|
return router.getAssignStatus(payload);
|
|
446
514
|
}
|
|
447
515
|
|
|
448
|
-
case
|
|
449
|
-
const teamInfo = getTeamBridgeMethod(
|
|
450
|
-
const result = teamInfo
|
|
516
|
+
case "team_info": {
|
|
517
|
+
const teamInfo = getTeamBridgeMethod("teamInfo");
|
|
518
|
+
const result = teamInfo
|
|
519
|
+
? await teamInfo(payload)
|
|
520
|
+
: teamInfoFallback(payload);
|
|
451
521
|
if (client) touchClient(client);
|
|
452
522
|
return result;
|
|
453
523
|
}
|
|
454
524
|
|
|
455
|
-
case
|
|
456
|
-
const teamTaskList = getTeamBridgeMethod(
|
|
457
|
-
const result = teamTaskList
|
|
525
|
+
case "team_task_list": {
|
|
526
|
+
const teamTaskList = getTeamBridgeMethod("teamTaskList");
|
|
527
|
+
const result = teamTaskList
|
|
528
|
+
? await teamTaskList(payload)
|
|
529
|
+
: teamTaskListFallback(payload);
|
|
458
530
|
if (client) touchClient(client);
|
|
459
531
|
return result;
|
|
460
532
|
}
|
|
461
533
|
|
|
462
|
-
case
|
|
534
|
+
case "pipeline_state": {
|
|
463
535
|
if (client) touchClient(client);
|
|
464
536
|
if (!store?.db) {
|
|
465
|
-
return { ok: false, error:
|
|
537
|
+
return { ok: false, error: "hub_db_not_found" };
|
|
466
538
|
}
|
|
467
539
|
ensurePipelineTable(store.db);
|
|
468
540
|
const state = readPipelineState(store.db, payload.team_name);
|
|
469
541
|
return state
|
|
470
542
|
? { ok: true, data: state }
|
|
471
|
-
: { ok: false, error:
|
|
543
|
+
: { ok: false, error: "pipeline_not_found" };
|
|
472
544
|
}
|
|
473
545
|
|
|
474
|
-
case
|
|
546
|
+
case "pipeline_list": {
|
|
475
547
|
if (client) touchClient(client);
|
|
476
548
|
if (!store?.db) {
|
|
477
|
-
return { ok: false, error:
|
|
549
|
+
return { ok: false, error: "hub_db_not_found" };
|
|
478
550
|
}
|
|
479
551
|
ensurePipelineTable(store.db);
|
|
480
552
|
return { ok: true, data: listPipelineStates(store.db) };
|
|
481
553
|
}
|
|
482
554
|
|
|
483
|
-
case
|
|
555
|
+
case "hitl_pending": {
|
|
556
|
+
if (client) touchClient(client);
|
|
557
|
+
if (!hitlManager) {
|
|
558
|
+
return { ok: false, error: "hitl not available" };
|
|
559
|
+
}
|
|
560
|
+
return { ok: true, data: hitlManager.getPendingRequests() };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
case "delegator_status": {
|
|
484
564
|
if (!delegatorService) {
|
|
485
|
-
return {
|
|
565
|
+
return {
|
|
566
|
+
ok: false,
|
|
567
|
+
error: {
|
|
568
|
+
code: "DELEGATOR_NOT_AVAILABLE",
|
|
569
|
+
message: "Delegator service가 초기화되지 않았습니다",
|
|
570
|
+
},
|
|
571
|
+
};
|
|
486
572
|
}
|
|
487
573
|
if (client) touchClient(client);
|
|
488
574
|
const result = await delegatorService.status(payload);
|
|
@@ -492,7 +578,10 @@ export function createPipeServer({
|
|
|
492
578
|
default:
|
|
493
579
|
return {
|
|
494
580
|
ok: false,
|
|
495
|
-
error: {
|
|
581
|
+
error: {
|
|
582
|
+
code: "UNKNOWN_PIPE_QUERY",
|
|
583
|
+
message: `지원하지 않는 query: ${action}`,
|
|
584
|
+
},
|
|
496
585
|
};
|
|
497
586
|
}
|
|
498
587
|
}
|
|
@@ -507,41 +596,56 @@ export function createPipeServer({
|
|
|
507
596
|
}
|
|
508
597
|
|
|
509
598
|
async function handleFrame(client, frame) {
|
|
510
|
-
if (!frame || typeof frame !==
|
|
599
|
+
if (!frame || typeof frame !== "object") {
|
|
511
600
|
return sendResponse(client, null, {
|
|
512
601
|
ok: false,
|
|
513
|
-
error: { code:
|
|
602
|
+
error: { code: "INVALID_FRAME", message: "JSON object frame required" },
|
|
514
603
|
});
|
|
515
604
|
}
|
|
516
605
|
|
|
517
606
|
if (!frame.type) {
|
|
518
607
|
return sendResponse(client, frame.request_id || null, {
|
|
519
608
|
ok: false,
|
|
520
|
-
error: { code:
|
|
609
|
+
error: { code: "INVALID_FRAME", message: "type required" },
|
|
521
610
|
});
|
|
522
611
|
}
|
|
523
612
|
|
|
524
613
|
touchClient(client);
|
|
525
614
|
|
|
526
615
|
try {
|
|
527
|
-
if (frame.type ===
|
|
616
|
+
if (frame.type === "command") {
|
|
528
617
|
const action = frame.payload?.action || frame.payload?.command;
|
|
529
|
-
const result = await processCommand(
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
618
|
+
const result = await processCommand(
|
|
619
|
+
client,
|
|
620
|
+
action,
|
|
621
|
+
frame.payload || {},
|
|
622
|
+
);
|
|
623
|
+
return sendResponse(
|
|
624
|
+
client,
|
|
625
|
+
frame.payload?.request_id || frame.request_id || null,
|
|
626
|
+
result,
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
if (frame.type === "query") {
|
|
533
630
|
const action = frame.payload?.action || frame.payload?.query;
|
|
534
631
|
const result = await processQuery(client, action, frame.payload || {});
|
|
535
|
-
return sendResponse(
|
|
632
|
+
return sendResponse(
|
|
633
|
+
client,
|
|
634
|
+
frame.payload?.request_id || frame.request_id || null,
|
|
635
|
+
result,
|
|
636
|
+
);
|
|
536
637
|
}
|
|
537
638
|
return sendResponse(client, frame.request_id || null, {
|
|
538
639
|
ok: false,
|
|
539
|
-
error: {
|
|
640
|
+
error: {
|
|
641
|
+
code: "INVALID_FRAME_TYPE",
|
|
642
|
+
message: `지원하지 않는 type: ${frame.type}`,
|
|
643
|
+
},
|
|
540
644
|
});
|
|
541
645
|
} catch (error) {
|
|
542
646
|
return sendResponse(client, frame.request_id || null, {
|
|
543
647
|
ok: false,
|
|
544
|
-
error: { code:
|
|
648
|
+
error: { code: "PIPE_REQUEST_FAILED", message: error.message },
|
|
545
649
|
});
|
|
546
650
|
}
|
|
547
651
|
}
|
|
@@ -550,7 +654,7 @@ export function createPipeServer({
|
|
|
550
654
|
const client = {
|
|
551
655
|
id: randomUUID(),
|
|
552
656
|
socket,
|
|
553
|
-
buffer:
|
|
657
|
+
buffer: "",
|
|
554
658
|
agentId: null,
|
|
555
659
|
subscriptions: new Set(),
|
|
556
660
|
lastHeartbeatMs: Date.now(),
|
|
@@ -558,38 +662,48 @@ export function createPipeServer({
|
|
|
558
662
|
};
|
|
559
663
|
clients.set(client.id, client);
|
|
560
664
|
|
|
561
|
-
socket.setEncoding(
|
|
562
|
-
socket.on(
|
|
665
|
+
socket.setEncoding("utf8");
|
|
666
|
+
socket.on("data", async (chunk) => {
|
|
563
667
|
client.buffer += chunk;
|
|
564
|
-
let newlineIndex = client.buffer.indexOf(
|
|
668
|
+
let newlineIndex = client.buffer.indexOf("\n");
|
|
565
669
|
while (newlineIndex >= 0) {
|
|
566
670
|
const line = client.buffer.slice(0, newlineIndex).trim();
|
|
567
671
|
client.buffer = client.buffer.slice(newlineIndex + 1);
|
|
568
672
|
if (line) {
|
|
569
|
-
|
|
570
|
-
|
|
673
|
+
try {
|
|
674
|
+
const frame = safeJsonParse(line);
|
|
675
|
+
await handleFrame(client, frame);
|
|
676
|
+
} catch (err) {
|
|
677
|
+
pipeLog.error(
|
|
678
|
+
{ clientId: client.id, err: String(err?.message || err) },
|
|
679
|
+
"pipe.frame_handler_error",
|
|
680
|
+
);
|
|
681
|
+
}
|
|
571
682
|
}
|
|
572
|
-
newlineIndex = client.buffer.indexOf(
|
|
683
|
+
newlineIndex = client.buffer.indexOf("\n");
|
|
573
684
|
}
|
|
574
685
|
});
|
|
575
686
|
|
|
576
|
-
socket.on(
|
|
577
|
-
socket.on(
|
|
687
|
+
socket.on("close", () => closeClient(client));
|
|
688
|
+
socket.on("error", () => closeClient(client));
|
|
578
689
|
}
|
|
579
690
|
|
|
580
691
|
function startHeartbeatMonitor() {
|
|
581
|
-
heartbeatTimer = setInterval(
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
692
|
+
heartbeatTimer = setInterval(
|
|
693
|
+
() => {
|
|
694
|
+
const now = Date.now();
|
|
695
|
+
for (const client of clients.values()) {
|
|
696
|
+
if (now - client.lastHeartbeatMs <= heartbeatTtlMs) continue;
|
|
697
|
+
sendFrame(client, {
|
|
698
|
+
type: "event",
|
|
699
|
+
event: "disconnect",
|
|
700
|
+
payload: { reason: "heartbeat_timeout" },
|
|
701
|
+
});
|
|
702
|
+
closeClient(client);
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
Math.max(1000, Math.floor(heartbeatTtlMs / 2)),
|
|
706
|
+
);
|
|
593
707
|
heartbeatTimer.unref();
|
|
594
708
|
}
|
|
595
709
|
|
|
@@ -600,16 +714,18 @@ export function createPipeServer({
|
|
|
600
714
|
if (server) return { path: pipePath };
|
|
601
715
|
|
|
602
716
|
if (!IS_WINDOWS && existsSync(pipePath)) {
|
|
603
|
-
try {
|
|
717
|
+
try {
|
|
718
|
+
unlinkSync(pipePath);
|
|
719
|
+
} catch {}
|
|
604
720
|
}
|
|
605
721
|
|
|
606
722
|
server = net.createServer(attachSocket);
|
|
607
|
-
router.deliveryEmitter.on(
|
|
723
|
+
router.deliveryEmitter.on("message", onMessage);
|
|
608
724
|
|
|
609
725
|
await new Promise((resolve, reject) => {
|
|
610
|
-
server.once(
|
|
726
|
+
server.once("error", reject);
|
|
611
727
|
server.listen(pipePath, () => {
|
|
612
|
-
server.off(
|
|
728
|
+
server.off("error", reject);
|
|
613
729
|
resolve();
|
|
614
730
|
});
|
|
615
731
|
});
|
|
@@ -624,7 +740,7 @@ export function createPipeServer({
|
|
|
624
740
|
heartbeatTimer = null;
|
|
625
741
|
}
|
|
626
742
|
|
|
627
|
-
router.deliveryEmitter.off(
|
|
743
|
+
router.deliveryEmitter.off("message", onMessage);
|
|
628
744
|
|
|
629
745
|
for (const client of clients.values()) {
|
|
630
746
|
closeClient(client);
|
|
@@ -637,14 +753,16 @@ export function createPipeServer({
|
|
|
637
753
|
}
|
|
638
754
|
|
|
639
755
|
if (!IS_WINDOWS && existsSync(pipePath)) {
|
|
640
|
-
try {
|
|
756
|
+
try {
|
|
757
|
+
unlinkSync(pipePath);
|
|
758
|
+
} catch {}
|
|
641
759
|
}
|
|
642
760
|
},
|
|
643
761
|
|
|
644
762
|
getStatus() {
|
|
645
763
|
return {
|
|
646
764
|
path: pipePath,
|
|
647
|
-
protocol:
|
|
765
|
+
protocol: "ndjson",
|
|
648
766
|
clients: clients.size,
|
|
649
767
|
pending_messages: Array.from(clients.values()).reduce((sum, client) => {
|
|
650
768
|
if (!client.agentId) return sum;
|