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/tools.mjs CHANGED
@@ -1,7 +1,14 @@
1
- // hub/tools.mjs — MCP 도구 8개 정의
2
- // register, status, publish, ask, poll_messages, handoff, request_human_input, submit_human_input
1
+ // hub/tools.mjs — MCP 도구 정의
2
+ // register/status/publish/ask/poll/handoff/HITL + team proxy
3
3
  // 모든 도구 응답: { ok: boolean, error?: { code, message }, data?: ... }
4
4
 
5
+ import {
6
+ teamInfo,
7
+ teamTaskList,
8
+ teamTaskUpdate,
9
+ teamSendMessage,
10
+ } from './team/nativeProxy.mjs';
11
+
5
12
  /**
6
13
  * MCP 도구 목록 생성
7
14
  * @param {object} store — createStore() 반환
@@ -145,19 +152,19 @@ export function createTools(store, router, hitl) {
145
152
  auto_ack: args.auto_ack,
146
153
  });
147
154
 
148
- // wait_ms > 0 이고 메시지 없으면 짧은 간격으로 반복 재시도
149
- if (!messages.length && args.wait_ms > 0) {
150
- const interval = Math.min(args.wait_ms, 500);
151
- const deadline = Date.now() + Math.min(args.wait_ms, 30000);
152
- while (!messages.length && Date.now() < deadline) {
153
- await new Promise(r => setTimeout(r, interval));
154
- messages = store.pollForAgent(args.agent_id, {
155
- max_messages: args.max_messages,
156
- include_topics: args.include_topics,
157
- auto_ack: args.auto_ack,
158
- });
159
- }
160
- }
155
+ // wait_ms > 0 이고 메시지 없으면 짧은 간격으로 반복 재시도
156
+ if (!messages.length && args.wait_ms > 0) {
157
+ const interval = Math.min(args.wait_ms, 500);
158
+ const deadline = Date.now() + Math.min(args.wait_ms, 30000);
159
+ while (!messages.length && Date.now() < deadline) {
160
+ await new Promise(r => setTimeout(r, interval));
161
+ messages = store.pollForAgent(args.agent_id, {
162
+ max_messages: args.max_messages,
163
+ include_topics: args.include_topics,
164
+ auto_ack: args.auto_ack,
165
+ });
166
+ }
167
+ }
161
168
 
162
169
  return {
163
170
  ok: true,
@@ -238,5 +245,96 @@ export function createTools(store, router, hitl) {
238
245
  return hitl.submitHumanInput(args);
239
246
  }),
240
247
  },
248
+
249
+ // ── 9. team_info ──
250
+ {
251
+ name: 'team_info',
252
+ description: 'Claude Native Teams 메타/멤버/경로 정보를 조회합니다',
253
+ inputSchema: {
254
+ type: 'object',
255
+ required: ['team_name'],
256
+ properties: {
257
+ team_name: { type: 'string', minLength: 1, maxLength: 128, pattern: '^[a-z0-9][a-z0-9-]*$' },
258
+ include_members: { type: 'boolean', default: true },
259
+ include_paths: { type: 'boolean', default: true },
260
+ },
261
+ },
262
+ handler: wrap('TEAM_INFO_FAILED', (args) => {
263
+ return teamInfo(args);
264
+ }),
265
+ },
266
+
267
+ // ── 10. team_task_list ──
268
+ {
269
+ name: 'team_task_list',
270
+ description: 'Claude Native Teams task 목록을 owner/status 조건으로 조회합니다',
271
+ inputSchema: {
272
+ type: 'object',
273
+ required: ['team_name'],
274
+ properties: {
275
+ team_name: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$' },
276
+ owner: { type: 'string' },
277
+ statuses: {
278
+ type: 'array',
279
+ items: { type: 'string', enum: ['pending', 'in_progress', 'completed', 'failed', 'deleted'] },
280
+ maxItems: 8,
281
+ },
282
+ include_internal: { type: 'boolean', default: false },
283
+ limit: { type: 'integer', minimum: 1, maximum: 1000, default: 200 },
284
+ },
285
+ },
286
+ handler: wrap('TEAM_TASK_LIST_FAILED', (args) => {
287
+ return teamTaskList(args);
288
+ }),
289
+ },
290
+
291
+ // ── 11. team_task_update ──
292
+ {
293
+ name: 'team_task_update',
294
+ description: 'Claude Native Teams task를 claim/update 합니다',
295
+ inputSchema: {
296
+ type: 'object',
297
+ required: ['team_name', 'task_id'],
298
+ properties: {
299
+ team_name: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$' },
300
+ task_id: { type: 'string', minLength: 1, maxLength: 64 },
301
+ claim: { type: 'boolean', default: false },
302
+ owner: { type: 'string' },
303
+ status: { type: 'string', enum: ['pending', 'in_progress', 'completed', 'failed', 'deleted'] },
304
+ subject: { type: 'string' },
305
+ description: { type: 'string' },
306
+ activeForm: { type: 'string' },
307
+ add_blocks: { type: 'array', items: { type: 'string' } },
308
+ add_blocked_by: { type: 'array', items: { type: 'string' } },
309
+ metadata_patch: { type: 'object' },
310
+ if_match_mtime_ms: { type: 'number' },
311
+ actor: { type: 'string' },
312
+ },
313
+ },
314
+ handler: wrap('TEAM_TASK_UPDATE_FAILED', (args) => {
315
+ return teamTaskUpdate(args);
316
+ }),
317
+ },
318
+
319
+ // ── 12. team_send_message ──
320
+ {
321
+ name: 'team_send_message',
322
+ description: 'Claude Native Teams inbox에 메시지를 append 합니다',
323
+ inputSchema: {
324
+ type: 'object',
325
+ required: ['team_name', 'from', 'text'],
326
+ properties: {
327
+ team_name: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$' },
328
+ from: { type: 'string', minLength: 1, maxLength: 128 },
329
+ to: { type: 'string', default: 'team-lead' },
330
+ text: { type: 'string', minLength: 1, maxLength: 200000 },
331
+ summary: { type: 'string', maxLength: 1000 },
332
+ color: { type: 'string', default: 'blue' },
333
+ },
334
+ },
335
+ handler: wrap('TEAM_SEND_MESSAGE_FAILED', (args) => {
336
+ return teamSendMessage(args);
337
+ }),
338
+ },
241
339
  ];
242
340
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "3.1.0-dev.5",
3
+ "version": "3.2.0-dev.2",
4
4
  "description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/setup.mjs CHANGED
@@ -10,6 +10,25 @@ import { homedir } from "os";
10
10
 
11
11
  const PLUGIN_ROOT = dirname(dirname(new URL(import.meta.url).pathname)).replace(/^\/([A-Z]:)/, "$1");
12
12
  const CLAUDE_DIR = join(homedir(), ".claude");
13
+ const CODEX_DIR = join(homedir(), ".codex");
14
+ const CODEX_CONFIG_PATH = join(CODEX_DIR, "config.toml");
15
+
16
+ const REQUIRED_CODEX_PROFILES = [
17
+ {
18
+ name: "xhigh",
19
+ lines: [
20
+ 'model = "gpt-5.3-codex"',
21
+ 'model_reasoning_effort = "xhigh"',
22
+ ],
23
+ },
24
+ {
25
+ name: "spark_fast",
26
+ lines: [
27
+ 'model = "gpt-5.1-codex-mini"',
28
+ 'model_reasoning_effort = "low"',
29
+ ],
30
+ },
31
+ ];
13
32
 
14
33
  // ── 파일 동기화 ──
15
34
 
@@ -41,6 +60,45 @@ function getVersion(filePath) {
41
60
  }
42
61
  }
43
62
 
63
+ function escapeRegExp(value) {
64
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
65
+ }
66
+
67
+ function hasProfileSection(tomlContent, profileName) {
68
+ const section = `^\\[profiles\\.${escapeRegExp(profileName)}\\]\\s*$`;
69
+ return new RegExp(section, "m").test(tomlContent);
70
+ }
71
+
72
+ function ensureCodexProfiles() {
73
+ try {
74
+ if (!existsSync(CODEX_DIR)) mkdirSync(CODEX_DIR, { recursive: true });
75
+
76
+ const original = existsSync(CODEX_CONFIG_PATH)
77
+ ? readFileSync(CODEX_CONFIG_PATH, "utf8")
78
+ : "";
79
+
80
+ let updated = original;
81
+ let added = 0;
82
+
83
+ for (const profile of REQUIRED_CODEX_PROFILES) {
84
+ if (hasProfileSection(updated, profile.name)) continue;
85
+
86
+ if (updated.length > 0 && !updated.endsWith("\n")) updated += "\n";
87
+ if (updated.trim().length > 0) updated += "\n";
88
+ updated += `[profiles.${profile.name}]\n${profile.lines.join("\n")}\n`;
89
+ added++;
90
+ }
91
+
92
+ if (added > 0) {
93
+ writeFileSync(CODEX_CONFIG_PATH, updated, "utf8");
94
+ }
95
+
96
+ return added;
97
+ } catch {
98
+ return 0;
99
+ }
100
+ }
101
+
44
102
  let synced = 0;
45
103
 
46
104
  for (const { src, dst, label } of SYNC_MAP) {
@@ -130,6 +188,36 @@ if (existsSync(hudPath)) {
130
188
  }
131
189
  }
132
190
 
191
+ // ── Agent Teams 환경변수 자동 설정 ──
192
+
193
+ try {
194
+ let agentSettings = {};
195
+ if (existsSync(settingsPath)) {
196
+ agentSettings = JSON.parse(readFileSync(settingsPath, "utf8"));
197
+ }
198
+
199
+ if (!agentSettings.env) agentSettings.env = {};
200
+ let agentSettingsChanged = false;
201
+
202
+ if (agentSettings.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
203
+ agentSettings.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
204
+ agentSettingsChanged = true;
205
+ }
206
+
207
+ // teammateMode: auto (tmux 밖이면 in-process, 안이면 split-pane)
208
+ if (!agentSettings.teammateMode) {
209
+ agentSettings.teammateMode = "auto";
210
+ agentSettingsChanged = true;
211
+ }
212
+
213
+ if (agentSettingsChanged) {
214
+ writeFileSync(settingsPath, JSON.stringify(agentSettings, null, 2) + "\n", "utf8");
215
+ synced++;
216
+ }
217
+ } catch {
218
+ // settings.json 파싱 실패 시 무시 — 기존 설정 보존
219
+ }
220
+
133
221
  // ── Stale PID 파일 정리 (hub 좀비 방지) ──
134
222
 
135
223
  const HUB_PID_FILE = join(CLAUDE_DIR, "cache", "tfx-hub", "hub.pid");
@@ -196,6 +284,13 @@ if (process.platform === "win32") {
196
284
  }
197
285
  }
198
286
 
287
+ // ── Codex 프로필 자동 보정 ──
288
+
289
+ const codexProfilesAdded = ensureCodexProfiles();
290
+ if (codexProfilesAdded > 0) {
291
+ synced++;
292
+ }
293
+
199
294
  // ── MCP 인벤토리 백그라운드 갱신 ──
200
295
 
201
296
  import { spawn } from "child_process";
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ // scripts/team-keyword.mjs — "team" 매직 키워드 → /tfx-team 라우팅
3
+ // UserPromptSubmit 훅에서 실행. stdin으로 프롬프트 수신.
4
+ import { readFileSync } from "node:fs";
5
+
6
+ // stdin에서 프롬프트 읽기
7
+ let prompt = "";
8
+ try {
9
+ prompt = readFileSync(0, "utf8");
10
+ } catch {
11
+ process.exit(0);
12
+ }
13
+
14
+ // 코드 블록 제거 (오탐 방지)
15
+ const cleaned = prompt
16
+ .replace(/```[\s\S]*?```/g, "")
17
+ .replace(/`[^`]+`/g, "")
18
+ .replace(/https?:\/\/\S+/g, "");
19
+
20
+ // "team" 키워드 감지 (소유격/관사 뒤는 제외)
21
+ const hasTeam =
22
+ /(?<!\b(?:my|the|our|a|his|her|their|its|omc|oh-my-claudecode)\s)\bteam\b/i.test(cleaned) ||
23
+ /\btfx[\s-]?team\b/i.test(cleaned);
24
+
25
+ if (hasTeam) {
26
+ console.log("[MAGIC KEYWORD: tfx-team]");
27
+ console.log("");
28
+ console.log("You MUST invoke the skill using the Skill tool:");
29
+ console.log("");
30
+ console.log("Skill: tfx-team");
31
+ console.log("");
32
+ console.log(`User request:\n${prompt.trim()}`);
33
+ console.log("");
34
+ console.log("IMPORTANT: Invoke the skill IMMEDIATELY. Do not proceed without loading the skill instructions.");
35
+ }
@@ -38,38 +38,6 @@ STDERR_LOG="/tmp/tfx-route-${AGENT_TYPE}-${TIMESTAMP}-stderr.log"
38
38
  STDOUT_LOG="/tmp/tfx-route-${AGENT_TYPE}-${TIMESTAMP}-stdout.log"
39
39
  TFX_TMP="${TMPDIR:-/tmp}"
40
40
 
41
- # ── Hub 브릿지 (선택적 — Hub 미실행 시 무시) ──
42
- # 패키지 내 브릿지 탐색 (npm global / git local 모두 대응)
43
- find_bridge() {
44
- # 1. 환경변수 지정
45
- [[ -n "${TFX_BRIDGE:-}" && -f "$TFX_BRIDGE" ]] && echo "$TFX_BRIDGE" && return
46
- # 2. 같은 패키지 내
47
- local script_dir
48
- script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
49
- local pkg_bridge="${script_dir}/../hub/bridge.mjs"
50
- [[ -f "$pkg_bridge" ]] && echo "$pkg_bridge" && return
51
- # 3. 설치된 triflux 패키지
52
- local npm_bridge
53
- npm_bridge="$(npm root -g 2>/dev/null)/triflux/hub/bridge.mjs"
54
- [[ -f "$npm_bridge" ]] && echo "$npm_bridge" && return
55
- echo ""
56
- }
57
- BRIDGE_BIN="$(find_bridge)"
58
- HUB_ENABLED="false"
59
- if [[ -n "$BRIDGE_BIN" ]]; then
60
- # Hub 핑 (3초 타임아웃, 실패 시 무시)
61
- HUB_PING=$(node "$BRIDGE_BIN" ping 2>/dev/null || echo '{"ok":false}')
62
- if echo "$HUB_PING" | grep -q '"ok":true'; then
63
- HUB_ENABLED="true"
64
- fi
65
- fi
66
-
67
- # Hub 브릿지 래퍼 (Hub 꺼져있으면 아무것도 안 함)
68
- hub_bridge() {
69
- [[ "$HUB_ENABLED" != "true" ]] && return 0
70
- node "$BRIDGE_BIN" "$@" 2>/dev/null || true
71
- }
72
-
73
41
  # fallback 시 원래 에이전트 정보 보존
74
42
  ORIGINAL_AGENT=""
75
43
  ORIGINAL_CLI_ARGS=""
@@ -99,7 +67,7 @@ route_agent() {
99
67
  CLI_EFFORT="high"; DEFAULT_TIMEOUT=1080; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
100
68
  build-fixer)
101
69
  CLI_TYPE="codex"; CLI_CMD="codex"
102
- CLI_ARGS="--profile fast exec ${codex_base}"
70
+ CLI_ARGS="exec --profile fast ${codex_base}"
103
71
  CLI_EFFORT="fast"; DEFAULT_TIMEOUT=540; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
104
72
  debugger)
105
73
  CLI_TYPE="codex"; CLI_CMD="codex"
@@ -107,39 +75,39 @@ route_agent() {
107
75
  CLI_EFFORT="high"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
108
76
  deep-executor)
109
77
  CLI_TYPE="codex"; CLI_CMD="codex"
110
- CLI_ARGS="--profile xhigh exec ${codex_base}"
78
+ CLI_ARGS="exec --profile xhigh ${codex_base}"
111
79
  CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="bg"; OPUS_OVERSIGHT="true" ;;
112
80
 
113
81
  # ─── 설계/분석 레인 ───
114
82
  architect)
115
83
  CLI_TYPE="codex"; CLI_CMD="codex"
116
- CLI_ARGS="--profile xhigh exec ${codex_base}"
84
+ CLI_ARGS="exec --profile xhigh ${codex_base}"
117
85
  CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="bg"; OPUS_OVERSIGHT="true" ;;
118
86
  planner)
119
87
  CLI_TYPE="codex"; CLI_CMD="codex"
120
- CLI_ARGS="--profile xhigh exec ${codex_base}"
88
+ CLI_ARGS="exec --profile xhigh ${codex_base}"
121
89
  CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="fg"; OPUS_OVERSIGHT="true" ;;
122
90
  critic)
123
91
  CLI_TYPE="codex"; CLI_CMD="codex"
124
- CLI_ARGS="--profile xhigh exec ${codex_base}"
92
+ CLI_ARGS="exec --profile xhigh ${codex_base}"
125
93
  CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="bg"; OPUS_OVERSIGHT="true" ;;
126
94
  analyst)
127
95
  CLI_TYPE="codex"; CLI_CMD="codex"
128
- CLI_ARGS="--profile xhigh exec ${codex_base}"
96
+ CLI_ARGS="exec --profile xhigh ${codex_base}"
129
97
  CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="fg"; OPUS_OVERSIGHT="true" ;;
130
98
 
131
99
  # ─── 리뷰 레인 ───
132
100
  code-reviewer)
133
101
  CLI_TYPE="codex"; CLI_CMD="codex"
134
- CLI_ARGS="--profile thorough exec ${codex_base} review"
102
+ CLI_ARGS="exec --profile thorough ${codex_base} review"
135
103
  CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=1800; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
136
104
  security-reviewer)
137
105
  CLI_TYPE="codex"; CLI_CMD="codex"
138
- CLI_ARGS="--profile thorough exec ${codex_base} review"
106
+ CLI_ARGS="exec --profile thorough ${codex_base} review"
139
107
  CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=1800; RUN_MODE="bg"; OPUS_OVERSIGHT="true" ;;
140
108
  quality-reviewer)
141
109
  CLI_TYPE="codex"; CLI_CMD="codex"
142
- CLI_ARGS="--profile thorough exec ${codex_base} review"
110
+ CLI_ARGS="exec --profile thorough ${codex_base} review"
143
111
  CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=1800; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
144
112
 
145
113
  # ─── 리서치 레인 ───
@@ -149,7 +117,7 @@ route_agent() {
149
117
  CLI_EFFORT="high"; DEFAULT_TIMEOUT=1440; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
150
118
  scientist-deep)
151
119
  CLI_TYPE="codex"; CLI_CMD="codex"
152
- CLI_ARGS="--profile thorough exec ${codex_base}"
120
+ CLI_ARGS="exec --profile thorough ${codex_base}"
153
121
  CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=3600; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
154
122
  document-specialist)
155
123
  CLI_TYPE="codex"; CLI_CMD="codex"
@@ -183,7 +151,7 @@ route_agent() {
183
151
  # ─── 경량 ───
184
152
  spark)
185
153
  CLI_TYPE="codex"; CLI_CMD="codex"
186
- CLI_ARGS="--profile spark_fast exec ${codex_base}"
154
+ CLI_ARGS="exec --profile spark_fast ${codex_base}"
187
155
  CLI_EFFORT="spark_fast"; DEFAULT_TIMEOUT=180; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
188
156
  *)
189
157
  echo "ERROR: 알 수 없는 에이전트 타입: $agent" >&2
@@ -208,7 +176,7 @@ apply_cli_mode() {
208
176
  designer)
209
177
  CLI_ARGS="exec ${codex_base}"; CLI_EFFORT="high"; DEFAULT_TIMEOUT=600 ;;
210
178
  writer)
211
- CLI_ARGS="--profile spark_fast exec ${codex_base}"; CLI_EFFORT="spark_fast"; DEFAULT_TIMEOUT=180 ;;
179
+ CLI_ARGS="exec --profile spark_fast ${codex_base}"; CLI_EFFORT="spark_fast"; DEFAULT_TIMEOUT=180 ;;
212
180
  esac
213
181
  echo "[tfx-route] TFX_CLI_MODE=codex: $AGENT_TYPE → codex($CLI_EFFORT)로 리매핑" >&2
214
182
  fi ;;
@@ -385,31 +353,11 @@ ${ctx_content}
385
353
 
386
354
  # 메타정보 (stderr)
387
355
  echo "[tfx-route] v${VERSION} type=$CLI_TYPE agent=$AGENT_TYPE effort=$CLI_EFFORT mode=$RUN_MODE timeout=${TIMEOUT_SEC}s" >&2
388
- echo "[tfx-route] opus_oversight=$OPUS_OVERSIGHT mcp_profile=$MCP_PROFILE hub=$HUB_ENABLED" >&2
356
+ echo "[tfx-route] opus_oversight=$OPUS_OVERSIGHT mcp_profile=$MCP_PROFILE" >&2
389
357
 
390
358
  # Per-process 에이전트 등록
391
359
  register_agent
392
360
 
393
- # Hub 브릿지: 에이전트 등록 (프로세스 수명 기반 lease)
394
- local hub_agent_id="${AGENT_TYPE}-$$"
395
- local hub_topics="${AGENT_TYPE},task.result"
396
- hub_bridge register \
397
- --agent "$hub_agent_id" \
398
- --cli "$CLI_TYPE" \
399
- --timeout "$TIMEOUT_SEC" \
400
- --topics "$hub_topics" \
401
- --capabilities "code,${AGENT_TYPE}"
402
-
403
- # Hub 브릿지: 선행 컨텍스트 폴링 (DAG 의존 태스크용)
404
- if [[ "$HUB_ENABLED" == "true" && -z "$CONTEXT_FILE" ]]; then
405
- local hub_ctx_file="${TFX_TMP}/tfx-hub-ctx-${hub_agent_id}.md"
406
- hub_bridge context --agent "$hub_agent_id" --topics "$hub_topics" --out "$hub_ctx_file"
407
- if [[ -s "$hub_ctx_file" ]]; then
408
- CONTEXT_FILE="$hub_ctx_file"
409
- echo "[tfx-route] hub: 선행 컨텍스트 수신 ($(wc -c < "$hub_ctx_file") bytes)" >&2
410
- fi
411
- fi
412
-
413
361
  # CLI 실행 (stderr 분리 + 타임아웃 + 소요시간 측정)
414
362
  local exit_code=0
415
363
  local start_time
@@ -466,14 +414,6 @@ ${ctx_content}
466
414
  end_time=$(date +%s)
467
415
  local elapsed=$((end_time - start_time))
468
416
 
469
- # Hub 브릿지: 결과 발행 + 에이전트 해제
470
- hub_bridge result \
471
- --agent "$hub_agent_id" \
472
- --file "$STDOUT_LOG" \
473
- --topic "task.result" \
474
- --exit-code "$exit_code"
475
- hub_bridge deregister --agent "$hub_agent_id"
476
-
477
417
  # ── 후처리: 단일 node 프로세스로 위임 ──
478
418
  # 토큰 추출, 출력 필터링, 로그, 토큰 누적, AIMD, 이슈 추적, 결과 출력 전부 처리
479
419
  local post_script="${HOME}/.claude/scripts/tfx-route-post.mjs"