triflux 3.2.0-dev.1 → 3.2.0-dev.10
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/README.ko.md +26 -18
- package/README.md +26 -18
- package/bin/triflux.mjs +1614 -1084
- package/hooks/hooks.json +12 -0
- package/hooks/keyword-rules.json +354 -0
- package/hub/bridge.mjs +371 -193
- package/hub/hitl.mjs +45 -31
- package/hub/pipe.mjs +457 -0
- package/hub/router.mjs +422 -161
- package/hub/server.mjs +429 -344
- package/hub/store.mjs +388 -314
- package/hub/team/cli-team-common.mjs +348 -0
- package/hub/team/cli-team-control.mjs +393 -0
- package/hub/team/cli-team-start.mjs +516 -0
- package/hub/team/cli-team-status.mjs +269 -0
- package/hub/team/cli.mjs +99 -368
- package/hub/team/dashboard.mjs +165 -64
- package/hub/team/native-supervisor.mjs +300 -0
- package/hub/team/native.mjs +62 -0
- package/hub/team/nativeProxy.mjs +534 -0
- package/hub/team/orchestrator.mjs +99 -35
- package/hub/team/pane.mjs +138 -101
- package/hub/team/psmux.mjs +297 -0
- package/hub/team/session.mjs +608 -186
- package/hub/team/shared.mjs +13 -0
- package/hub/team/staleState.mjs +299 -0
- package/hub/tools.mjs +140 -53
- package/hub/workers/claude-worker.mjs +446 -0
- package/hub/workers/codex-mcp.mjs +414 -0
- package/hub/workers/factory.mjs +18 -0
- package/hub/workers/gemini-worker.mjs +349 -0
- package/hub/workers/interface.mjs +41 -0
- package/hud/hud-qos-status.mjs +1789 -1732
- package/package.json +6 -2
- package/scripts/__tests__/keyword-detector.test.mjs +234 -0
- package/scripts/hub-ensure.mjs +83 -0
- package/scripts/keyword-detector.mjs +272 -0
- package/scripts/keyword-rules-expander.mjs +521 -0
- package/scripts/lib/keyword-rules.mjs +168 -0
- package/scripts/psmux-steering-prototype.sh +368 -0
- package/scripts/run.cjs +62 -0
- package/scripts/setup.mjs +189 -7
- package/scripts/test-tfx-route-no-claude-native.mjs +49 -0
- package/scripts/tfx-route-worker.mjs +161 -0
- package/scripts/tfx-route.sh +943 -508
- package/skills/tfx-auto/SKILL.md +90 -564
- package/skills/tfx-auto-codex/SKILL.md +77 -0
- package/skills/tfx-codex/SKILL.md +1 -4
- package/skills/tfx-doctor/SKILL.md +1 -0
- package/skills/tfx-gemini/SKILL.md +1 -4
- package/skills/tfx-multi/SKILL.md +296 -0
- package/skills/tfx-setup/SKILL.md +1 -4
- package/skills/tfx-team/SKILL.md +0 -172
package/hub/bridge.mjs
CHANGED
|
@@ -1,72 +1,152 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// hub/bridge.mjs — tfx-route.sh ↔ tfx-hub 브릿지 CLI
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// node bridge.mjs register --agent <id> --cli <type> --timeout <sec> [--topics t1,t2]
|
|
9
|
-
// node bridge.mjs result --agent <id> --file <path> [--topic task.result] [--trace <id>]
|
|
10
|
-
// node bridge.mjs context --agent <id> [--topics t1,t2] [--max 10] [--out <path>]
|
|
11
|
-
// node bridge.mjs deregister --agent <id>
|
|
12
|
-
// node bridge.mjs ping
|
|
13
|
-
//
|
|
14
|
-
// Hub 미실행 시 모든 커맨드는 조용히 실패 (exit 0).
|
|
15
|
-
// tfx-route.sh 흐름을 절대 차단하지 않는다.
|
|
16
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// hub/bridge.mjs — tfx-route.sh ↔ tfx-hub 브릿지 CLI
|
|
3
|
+
//
|
|
4
|
+
// Named Pipe/Unix Socket 제어 채널을 우선 사용하고,
|
|
5
|
+
// 연결이 없을 때만 HTTP /bridge/* 엔드포인트로 내려간다.
|
|
6
|
+
|
|
7
|
+
import net from 'node:net';
|
|
17
8
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
18
9
|
import { join } from 'node:path';
|
|
19
10
|
import { homedir } from 'node:os';
|
|
20
11
|
import { parseArgs as nodeParseArgs } from 'node:util';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// 기본값
|
|
12
|
+
import { randomUUID } from 'node:crypto';
|
|
13
|
+
|
|
14
|
+
const HUB_PID_FILE = join(homedir(), '.claude', 'cache', 'tfx-hub', 'hub.pid');
|
|
15
|
+
|
|
16
|
+
export function getHubUrl() {
|
|
17
|
+
if (process.env.TFX_HUB_URL) return process.env.TFX_HUB_URL.replace(/\/mcp$/, '');
|
|
18
|
+
|
|
19
|
+
if (existsSync(HUB_PID_FILE)) {
|
|
20
|
+
try {
|
|
21
|
+
const info = JSON.parse(readFileSync(HUB_PID_FILE, 'utf8'));
|
|
22
|
+
return `http://${info.host || '127.0.0.1'}:${info.port || 27888}`;
|
|
23
|
+
} catch {
|
|
24
|
+
// 무시
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
39
28
|
const port = process.env.TFX_HUB_PORT || '27888';
|
|
40
29
|
return `http://127.0.0.1:${port}`;
|
|
41
30
|
}
|
|
42
31
|
|
|
43
|
-
|
|
32
|
+
export function getHubPipePath() {
|
|
33
|
+
if (process.env.TFX_HUB_PIPE) return process.env.TFX_HUB_PIPE;
|
|
44
34
|
|
|
45
|
-
|
|
35
|
+
if (!existsSync(HUB_PID_FILE)) return null;
|
|
36
|
+
try {
|
|
37
|
+
const info = JSON.parse(readFileSync(HUB_PID_FILE, 'utf8'));
|
|
38
|
+
return info.pipe_path || info.pipePath || null;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
46
43
|
|
|
47
|
-
async function post(path, body, timeoutMs = 5000) {
|
|
48
|
-
const url = `${_cachedHubUrl}${path}`;
|
|
44
|
+
export async function post(path, body, timeoutMs = 5000) {
|
|
49
45
|
const controller = new AbortController();
|
|
50
46
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const res = await fetch(
|
|
54
|
-
method: 'POST',
|
|
55
|
-
headers: { 'Content-Type': 'application/json' },
|
|
56
|
-
body: JSON.stringify(body),
|
|
57
|
-
signal: controller.signal,
|
|
58
|
-
});
|
|
59
|
-
clearTimeout(timer);
|
|
60
|
-
return await res.json();
|
|
61
|
-
} catch {
|
|
62
|
-
clearTimeout(timer);
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch(`${getHubUrl()}${path}`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { 'Content-Type': 'application/json' },
|
|
52
|
+
body: JSON.stringify(body),
|
|
53
|
+
signal: controller.signal,
|
|
54
|
+
});
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
return await res.json();
|
|
57
|
+
} catch {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function connectPipe(timeoutMs = 1200) {
|
|
64
|
+
const pipePath = getHubPipePath();
|
|
65
|
+
if (!pipePath) return null;
|
|
66
|
+
|
|
67
|
+
return await new Promise((resolve) => {
|
|
68
|
+
const socket = net.createConnection(pipePath);
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
try { socket.destroy(); } catch {}
|
|
71
|
+
resolve(null);
|
|
72
|
+
}, timeoutMs);
|
|
73
|
+
|
|
74
|
+
socket.once('connect', () => {
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
socket.setEncoding('utf8');
|
|
77
|
+
resolve(socket);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
socket.once('error', () => {
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
try { socket.destroy(); } catch {}
|
|
83
|
+
resolve(null);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function pipeRequest(type, action, payload, timeoutMs = 3000) {
|
|
89
|
+
const socket = await connectPipe(Math.min(timeoutMs, 1500));
|
|
90
|
+
if (!socket) return null;
|
|
91
|
+
|
|
92
|
+
return await new Promise((resolve) => {
|
|
93
|
+
const requestId = randomUUID();
|
|
94
|
+
let buffer = '';
|
|
95
|
+
const timer = setTimeout(() => {
|
|
96
|
+
try { socket.destroy(); } catch {}
|
|
97
|
+
resolve(null);
|
|
98
|
+
}, timeoutMs);
|
|
99
|
+
|
|
100
|
+
const finish = (result) => {
|
|
101
|
+
clearTimeout(timer);
|
|
102
|
+
try { socket.end(); } catch {}
|
|
103
|
+
resolve(result);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
socket.on('data', (chunk) => {
|
|
107
|
+
buffer += chunk;
|
|
108
|
+
let newlineIndex = buffer.indexOf('\n');
|
|
109
|
+
while (newlineIndex >= 0) {
|
|
110
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
111
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
112
|
+
newlineIndex = buffer.indexOf('\n');
|
|
113
|
+
if (!line) continue;
|
|
114
|
+
|
|
115
|
+
let frame;
|
|
116
|
+
try {
|
|
117
|
+
frame = JSON.parse(line);
|
|
118
|
+
} catch {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (frame?.type !== 'response' || frame.request_id !== requestId) continue;
|
|
123
|
+
finish({
|
|
124
|
+
ok: frame.ok,
|
|
125
|
+
error: frame.error,
|
|
126
|
+
data: frame.data,
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
socket.on('error', () => finish(null));
|
|
133
|
+
socket.write(JSON.stringify({
|
|
134
|
+
type,
|
|
135
|
+
request_id: requestId,
|
|
136
|
+
payload: { action, ...payload },
|
|
137
|
+
}) + '\n');
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function pipeCommand(action, payload, timeoutMs = 3000) {
|
|
142
|
+
return await pipeRequest('command', action, payload, timeoutMs);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function pipeQuery(action, payload, timeoutMs = 3000) {
|
|
146
|
+
return await pipeRequest('query', action, payload, timeoutMs);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function parseArgs(argv) {
|
|
70
150
|
const { values } = nodeParseArgs({
|
|
71
151
|
args: argv,
|
|
72
152
|
options: {
|
|
@@ -82,151 +162,249 @@ function parseArgs(argv) {
|
|
|
82
162
|
'exit-code': { type: 'string' },
|
|
83
163
|
max: { type: 'string' },
|
|
84
164
|
out: { type: 'string' },
|
|
165
|
+
team: { type: 'string' },
|
|
166
|
+
'task-id': { type: 'string' },
|
|
167
|
+
owner: { type: 'string' },
|
|
168
|
+
status: { type: 'string' },
|
|
169
|
+
statuses: { type: 'string' },
|
|
170
|
+
claim: { type: 'boolean' },
|
|
171
|
+
actor: { type: 'string' },
|
|
172
|
+
from: { type: 'string' },
|
|
173
|
+
to: { type: 'string' },
|
|
174
|
+
text: { type: 'string' },
|
|
175
|
+
summary: { type: 'string' },
|
|
176
|
+
color: { type: 'string' },
|
|
177
|
+
limit: { type: 'string' },
|
|
178
|
+
'include-internal': { type: 'boolean' },
|
|
179
|
+
subject: { type: 'string' },
|
|
180
|
+
description: { type: 'string' },
|
|
181
|
+
'active-form': { type: 'string' },
|
|
182
|
+
'add-blocks': { type: 'string' },
|
|
183
|
+
'add-blocked-by': { type: 'string' },
|
|
184
|
+
'metadata-patch': { type: 'string' },
|
|
185
|
+
'if-match-mtime-ms': { type: 'string' },
|
|
85
186
|
},
|
|
86
187
|
strict: false,
|
|
87
188
|
});
|
|
88
189
|
return values;
|
|
89
190
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const exitCode = parseInt(args['exit-code'] || '0', 10);
|
|
128
|
-
|
|
129
|
-
// 결과 파일 읽기 (최대 48KB — Hub 메시지 크기 제한)
|
|
130
|
-
let output = '';
|
|
131
|
-
if (filePath && existsSync(filePath)) {
|
|
132
|
-
output = readFileSync(filePath, 'utf8').slice(0, 49152);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const result = await post('/bridge/result', {
|
|
136
|
-
agent_id: agentId,
|
|
137
|
-
topic,
|
|
138
|
-
payload: {
|
|
191
|
+
|
|
192
|
+
export function parseJsonSafe(raw, fallback = null) {
|
|
193
|
+
if (!raw) return fallback;
|
|
194
|
+
try {
|
|
195
|
+
return JSON.parse(raw);
|
|
196
|
+
} catch {
|
|
197
|
+
return fallback;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function runPipeFirst(commandName, queryName, httpPath, body, timeoutMs = 3000) {
|
|
202
|
+
const viaPipe = commandName
|
|
203
|
+
? await pipeCommand(commandName, body, timeoutMs)
|
|
204
|
+
: await pipeQuery(queryName, body, timeoutMs);
|
|
205
|
+
if (viaPipe) return viaPipe;
|
|
206
|
+
return await post(httpPath, body, Math.max(timeoutMs, 5000));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function cmdRegister(args) {
|
|
210
|
+
const agentId = args.agent;
|
|
211
|
+
const timeoutSec = parseInt(args.timeout || '600', 10);
|
|
212
|
+
const result = await runPipeFirst('register', null, '/bridge/register', {
|
|
213
|
+
agent_id: agentId,
|
|
214
|
+
cli: args.cli || 'other',
|
|
215
|
+
timeout_sec: timeoutSec,
|
|
216
|
+
heartbeat_ttl_ms: (timeoutSec + 120) * 1000,
|
|
217
|
+
topics: args.topics ? args.topics.split(',') : [],
|
|
218
|
+
capabilities: args.capabilities ? args.capabilities.split(',') : ['code'],
|
|
219
|
+
metadata: {
|
|
220
|
+
pid: process.ppid,
|
|
221
|
+
registered_at: Date.now(),
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if (result?.ok) {
|
|
226
|
+
console.log(JSON.stringify({
|
|
227
|
+
ok: true,
|
|
139
228
|
agent_id: agentId,
|
|
140
|
-
|
|
229
|
+
lease_expires_ms: result.data?.lease_expires_ms,
|
|
230
|
+
pipe_path: result.data?.pipe_path || getHubPipePath(),
|
|
231
|
+
}));
|
|
232
|
+
} else {
|
|
233
|
+
console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function cmdResult(args) {
|
|
238
|
+
let output = '';
|
|
239
|
+
if (args.file && existsSync(args.file)) {
|
|
240
|
+
output = readFileSync(args.file, 'utf8').slice(0, 49152);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const result = await runPipeFirst('result', null, '/bridge/result', {
|
|
244
|
+
agent_id: args.agent,
|
|
245
|
+
topic: args.topic || 'task.result',
|
|
246
|
+
payload: {
|
|
247
|
+
agent_id: args.agent,
|
|
248
|
+
exit_code: parseInt(args['exit-code'] || '0', 10),
|
|
141
249
|
output_length: output.length,
|
|
142
|
-
output_preview: output.slice(0, 4096),
|
|
143
|
-
output_file:
|
|
250
|
+
output_preview: output.slice(0, 4096),
|
|
251
|
+
output_file: args.file || null,
|
|
144
252
|
completed_at: Date.now(),
|
|
145
253
|
},
|
|
146
|
-
trace_id:
|
|
147
|
-
correlation_id:
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
if (result?.ok) {
|
|
151
|
-
console.log(JSON.stringify({ ok: true, message_id: result.data?.message_id }));
|
|
152
|
-
} else {
|
|
153
|
-
console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async function cmdContext(args) {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
254
|
+
trace_id: args.trace || undefined,
|
|
255
|
+
correlation_id: args.correlation || undefined,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
if (result?.ok) {
|
|
259
|
+
console.log(JSON.stringify({ ok: true, message_id: result.data?.message_id }));
|
|
260
|
+
} else {
|
|
261
|
+
console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function cmdContext(args) {
|
|
266
|
+
const result = await runPipeFirst(null, 'drain', '/bridge/context', {
|
|
267
|
+
agent_id: args.agent,
|
|
268
|
+
topics: args.topics ? args.topics.split(',') : undefined,
|
|
269
|
+
max_messages: parseInt(args.max || '10', 10),
|
|
270
|
+
auto_ack: true,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (result?.ok && result.data?.messages?.length) {
|
|
274
|
+
const parts = result.data.messages.map((message, index) => {
|
|
275
|
+
const payload = typeof message.payload === 'string'
|
|
276
|
+
? message.payload
|
|
277
|
+
: JSON.stringify(message.payload, null, 2);
|
|
278
|
+
return `=== Context ${index + 1}: ${message.from_agent || 'unknown'} (${message.topic || 'unknown'}) ===\n${payload}`;
|
|
279
|
+
});
|
|
280
|
+
const combined = parts.join('\n\n');
|
|
281
|
+
|
|
282
|
+
if (args.out) {
|
|
283
|
+
writeFileSync(args.out, combined, 'utf8');
|
|
284
|
+
console.log(JSON.stringify({ ok: true, count: result.data.messages.length, file: args.out }));
|
|
285
|
+
} else {
|
|
286
|
+
console.log(combined);
|
|
287
|
+
}
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (args.out) console.log(JSON.stringify({ ok: true, count: 0 }));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function cmdDeregister(args) {
|
|
295
|
+
const result = await runPipeFirst('deregister', null, '/bridge/deregister', {
|
|
296
|
+
agent_id: args.agent,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
if (result?.ok) {
|
|
300
|
+
console.log(JSON.stringify({ ok: true, agent_id: args.agent, status: 'offline' }));
|
|
301
|
+
} else {
|
|
302
|
+
console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function cmdTeamInfo(args) {
|
|
307
|
+
const result = await post('/bridge/team/info', {
|
|
308
|
+
team_name: args.team,
|
|
309
|
+
include_members: true,
|
|
310
|
+
include_paths: true,
|
|
311
|
+
});
|
|
312
|
+
console.log(JSON.stringify(result || { ok: false, reason: 'hub_unavailable' }));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function cmdTeamTaskList(args) {
|
|
316
|
+
const result = await post('/bridge/team/task-list', {
|
|
317
|
+
team_name: args.team,
|
|
318
|
+
owner: args.owner,
|
|
319
|
+
statuses: args.statuses ? args.statuses.split(',').map((status) => status.trim()).filter(Boolean) : [],
|
|
320
|
+
include_internal: !!args['include-internal'],
|
|
321
|
+
limit: parseInt(args.limit || '200', 10),
|
|
322
|
+
});
|
|
323
|
+
console.log(JSON.stringify(result || { ok: false, reason: 'hub_unavailable' }));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function cmdTeamTaskUpdate(args) {
|
|
327
|
+
const result = await post('/bridge/team/task-update', {
|
|
328
|
+
team_name: args.team,
|
|
329
|
+
task_id: args['task-id'],
|
|
330
|
+
claim: !!args.claim,
|
|
331
|
+
owner: args.owner,
|
|
332
|
+
status: args.status,
|
|
333
|
+
subject: args.subject,
|
|
334
|
+
description: args.description,
|
|
335
|
+
activeForm: args['active-form'],
|
|
336
|
+
add_blocks: args['add-blocks'] ? args['add-blocks'].split(',').map((value) => value.trim()).filter(Boolean) : undefined,
|
|
337
|
+
add_blocked_by: args['add-blocked-by'] ? args['add-blocked-by'].split(',').map((value) => value.trim()).filter(Boolean) : undefined,
|
|
338
|
+
metadata_patch: args['metadata-patch'] ? parseJsonSafe(args['metadata-patch'], null) : undefined,
|
|
339
|
+
if_match_mtime_ms: args['if-match-mtime-ms'] != null ? Number(args['if-match-mtime-ms']) : undefined,
|
|
340
|
+
actor: args.actor,
|
|
341
|
+
});
|
|
342
|
+
console.log(JSON.stringify(result || { ok: false, reason: 'hub_unavailable' }));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function cmdTeamSendMessage(args) {
|
|
346
|
+
const result = await post('/bridge/team/send-message', {
|
|
347
|
+
team_name: args.team,
|
|
348
|
+
from: args.from,
|
|
349
|
+
to: args.to || 'team-lead',
|
|
350
|
+
text: args.text,
|
|
351
|
+
summary: args.summary,
|
|
352
|
+
color: args.color || 'blue',
|
|
353
|
+
});
|
|
354
|
+
console.log(JSON.stringify(result || { ok: false, reason: 'hub_unavailable' }));
|
|
355
|
+
}
|
|
356
|
+
|
|
204
357
|
async function cmdPing() {
|
|
358
|
+
const viaPipe = await pipeQuery('status', { scope: 'hub' }, 2000);
|
|
359
|
+
if (viaPipe?.ok) {
|
|
360
|
+
console.log(JSON.stringify({
|
|
361
|
+
ok: true,
|
|
362
|
+
hub: viaPipe.data?.hub?.state || 'healthy',
|
|
363
|
+
pipe_path: getHubPipePath(),
|
|
364
|
+
transport: 'pipe',
|
|
365
|
+
}));
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
205
369
|
try {
|
|
206
|
-
const url = `${_cachedHubUrl}/status`;
|
|
207
370
|
const controller = new AbortController();
|
|
208
|
-
const timer = setTimeout(() => controller.abort(), 3000);
|
|
209
|
-
const res = await fetch(
|
|
210
|
-
clearTimeout(timer);
|
|
211
|
-
const data = await res.json();
|
|
212
|
-
console.log(JSON.stringify({
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
371
|
+
const timer = setTimeout(() => controller.abort(), 3000);
|
|
372
|
+
const res = await fetch(`${getHubUrl()}/status`, { signal: controller.signal });
|
|
373
|
+
clearTimeout(timer);
|
|
374
|
+
const data = await res.json();
|
|
375
|
+
console.log(JSON.stringify({
|
|
376
|
+
ok: true,
|
|
377
|
+
hub: data.hub?.state,
|
|
378
|
+
sessions: data.sessions,
|
|
379
|
+
pipe_path: data.pipe?.path || data.pipe_path || null,
|
|
380
|
+
transport: 'http',
|
|
381
|
+
}));
|
|
382
|
+
} catch {
|
|
383
|
+
console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
388
|
+
const cmd = argv[0];
|
|
389
|
+
const args = parseArgs(argv.slice(1));
|
|
390
|
+
|
|
391
|
+
switch (cmd) {
|
|
392
|
+
case 'register': await cmdRegister(args); break;
|
|
393
|
+
case 'result': await cmdResult(args); break;
|
|
394
|
+
case 'context': await cmdContext(args); break;
|
|
395
|
+
case 'deregister': await cmdDeregister(args); break;
|
|
396
|
+
case 'team-info': await cmdTeamInfo(args); break;
|
|
397
|
+
case 'team-task-list': await cmdTeamTaskList(args); break;
|
|
398
|
+
case 'team-task-update': await cmdTeamTaskUpdate(args); break;
|
|
399
|
+
case 'team-send-message': await cmdTeamSendMessage(args); break;
|
|
400
|
+
case 'ping': await cmdPing(args); break;
|
|
401
|
+
default:
|
|
402
|
+
console.error('사용법: bridge.mjs <register|result|context|deregister|team-info|team-task-list|team-task-update|team-send-message|ping> [--옵션]');
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const selfRun = process.argv[1]?.replace(/\\/g, '/').endsWith('hub/bridge.mjs');
|
|
408
|
+
if (selfRun) {
|
|
409
|
+
await main();
|
|
410
|
+
}
|