@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.
Files changed (162) hide show
  1. package/hub/pipe.mjs +239 -121
  2. package/hub/public/dashboard.html +61 -0
  3. package/hub/server.mjs +2179 -709
  4. package/hub/store-adapter.mjs +59 -26
  5. package/hub/store.mjs +356 -153
  6. package/hub/team/agent-map.json +26 -7
  7. package/hub/team/ansi.mjs +245 -146
  8. package/hub/team/backend.mjs +78 -17
  9. package/hub/team/build-worker-prompt.mjs +73 -0
  10. package/hub/team/check-mcp-hub.mjs +49 -0
  11. package/hub/team/claude-daemon-control.mjs +1130 -0
  12. package/hub/team/claude-native-bridge.mjs +1011 -0
  13. package/hub/team/claude-session-projection.mjs +74 -0
  14. package/hub/team/cli/commands/attach.mjs +57 -37
  15. package/hub/team/cli/commands/control.mjs +29 -8
  16. package/hub/team/cli/commands/debug.mjs +95 -74
  17. package/hub/team/cli/commands/focus.mjs +80 -53
  18. package/hub/team/cli/commands/interrupt.mjs +18 -6
  19. package/hub/team/cli/commands/kill.mjs +16 -5
  20. package/hub/team/cli/commands/list.mjs +31 -24
  21. package/hub/team/cli/commands/send.mjs +19 -6
  22. package/hub/team/cli/commands/start/index.mjs +151 -33
  23. package/hub/team/cli/commands/start/parse-args.mjs +97 -13
  24. package/hub/team/cli/commands/start/start-headless.mjs +155 -37
  25. package/hub/team/cli/commands/start/start-in-process.mjs +50 -40
  26. package/hub/team/cli/commands/start/start-mux.mjs +122 -73
  27. package/hub/team/cli/commands/start/start-wt.mjs +86 -69
  28. package/hub/team/cli/commands/status.mjs +43 -14
  29. package/hub/team/cli/commands/stop.mjs +11 -4
  30. package/hub/team/cli/commands/task.mjs +8 -3
  31. package/hub/team/cli/commands/tasks.mjs +13 -13
  32. package/hub/team/cli/help.mjs +1 -1
  33. package/hub/team/cli/index.mjs +2 -2
  34. package/hub/team/cli/manifest.mjs +38 -8
  35. package/hub/team/cli/render.mjs +52 -30
  36. package/hub/team/cli/services/attach-fallback.mjs +74 -54
  37. package/hub/team/cli/services/hub-client.mjs +44 -16
  38. package/hub/team/cli/services/member-selector.mjs +37 -30
  39. package/hub/team/cli/services/native-control.mjs +144 -116
  40. package/hub/team/cli/services/runtime-mode.mjs +61 -4
  41. package/hub/team/cli/services/state-store.mjs +66 -9
  42. package/hub/team/cli/services/task-model.mjs +40 -30
  43. package/hub/team/codex-review.mjs +521 -0
  44. package/hub/team/conductor-mesh-bridge.mjs +25 -24
  45. package/hub/team/conductor-registry.mjs +121 -0
  46. package/hub/team/conductor.mjs +1379 -671
  47. package/hub/team/consensus-meta.mjs +176 -0
  48. package/hub/team/dashboard-anchor.mjs +4 -5
  49. package/hub/team/dashboard-layout.mjs +3 -1
  50. package/hub/team/dashboard-open.mjs +20 -118
  51. package/hub/team/dashboard.mjs +76 -28
  52. package/hub/team/event-log.mjs +18 -10
  53. package/hub/team/execution-mode.mjs +256 -0
  54. package/hub/team/extract-completion-payload.mjs +110 -0
  55. package/hub/team/git-preflight.mjs +286 -0
  56. package/hub/team/handoff.mjs +49 -18
  57. package/hub/team/headless-bridge-session.mjs +17 -0
  58. package/hub/team/headless.mjs +2213 -1156
  59. package/hub/team/health-probe.mjs +232 -69
  60. package/hub/team/interactive-native-launcher.mjs +106 -0
  61. package/hub/team/interactive-native-worker.mjs +80 -0
  62. package/hub/team/interactive-tui-transport.mjs +465 -0
  63. package/hub/team/launcher-template.mjs +49 -17
  64. package/hub/team/lead-control.mjs +91 -4
  65. package/hub/team/mcp-selector.mjs +145 -0
  66. package/hub/team/native-supervisor.mjs +90 -26
  67. package/hub/team/native.mjs +120 -42
  68. package/hub/team/nativeProxy.mjs +184 -113
  69. package/hub/team/notify.mjs +147 -78
  70. package/hub/team/orchestrator.mjs +173 -161
  71. package/hub/team/pane.mjs +43 -20
  72. package/hub/team/process-cleanup.mjs +69 -25
  73. package/hub/team/psmux.mjs +1022 -281
  74. package/hub/team/recovery-store.mjs +149 -0
  75. package/hub/team/remote-probe.mjs +68 -52
  76. package/hub/team/remote-session.mjs +131 -59
  77. package/hub/team/remote-watcher.mjs +61 -33
  78. package/hub/team/retry-state-machine.mjs +336 -0
  79. package/hub/team/routing.mjs +51 -25
  80. package/hub/team/runtime-strategy.mjs +200 -0
  81. package/hub/team/schemas/worker-completion.json +39 -0
  82. package/hub/team/sentinel-capture.mjs +127 -0
  83. package/hub/team/session-sync.mjs +153 -6
  84. package/hub/team/session.mjs +574 -611
  85. package/hub/team/shared.mjs +13 -13
  86. package/hub/team/staleState.mjs +72 -30
  87. package/hub/team/swarm-cli.mjs +390 -0
  88. package/hub/team/swarm-hypervisor.mjs +1544 -193
  89. package/hub/team/swarm-intent.mjs +216 -0
  90. package/hub/team/swarm-locks.mjs +140 -25
  91. package/hub/team/swarm-planner.mjs +121 -21
  92. package/hub/team/swarm-preflight.mjs +319 -0
  93. package/hub/team/swarm-reconciler.mjs +128 -23
  94. package/hub/team/synapse-cli.mjs +458 -0
  95. package/hub/team/synapse-http.mjs +60 -0
  96. package/hub/team/synapse-registry.mjs +329 -0
  97. package/hub/team/terminal-opener.mjs +216 -0
  98. package/hub/team/tui-core.mjs +292 -0
  99. package/hub/team/tui-lite.mjs +237 -148
  100. package/hub/team/tui-remote-adapter.mjs +18 -34
  101. package/hub/team/tui-synapse.mjs +223 -0
  102. package/hub/team/tui-viewer.mjs +108 -48
  103. package/hub/team/tui-widgets.mjs +262 -0
  104. package/hub/team/tui.mjs +931 -439
  105. package/hub/team/worker-completion-validator.mjs +84 -0
  106. package/hub/team/worker-sandbox.mjs +97 -0
  107. package/hub/team/worker-signal.mjs +263 -0
  108. package/hub/team/worker-signal.types.d.ts +52 -0
  109. package/hub/team/worktree-lifecycle.mjs +517 -63
  110. package/hub/team/wt-manager.mjs +297 -26
  111. package/hub/tools.mjs +477 -252
  112. package/hub/tray.mjs +411 -368
  113. package/hub/workers/antigravity-route-worker.mjs +169 -0
  114. package/hub/workers/claude-worker.mjs +162 -118
  115. package/hub/workers/codex-app-server-worker.mjs +1015 -0
  116. package/hub/workers/codex-mcp.mjs +724 -507
  117. package/hub/workers/delegator-mcp.mjs +570 -499
  118. package/hub/workers/factory.mjs +103 -21
  119. package/hub/workers/gemini-worker.mjs +126 -284
  120. package/hub/workers/interface.mjs +24 -2
  121. package/hub/workers/lib/jsonrpc-stdio.mjs +463 -0
  122. package/hub/workers/worker-utils.mjs +23 -17
  123. package/package.json +4 -3
  124. package/scripts/lib/agent-json.mjs +27 -0
  125. package/scripts/lib/async.mjs +174 -0
  126. package/scripts/lib/claudemd-scanner.mjs +223 -0
  127. package/scripts/lib/cli-agy.mjs +62 -0
  128. package/scripts/lib/cli-claude.mjs +78 -0
  129. package/scripts/lib/cli-codex.mjs +199 -0
  130. package/scripts/lib/cli-gemini.mjs +37 -0
  131. package/scripts/lib/codex-recovery.sh +50 -0
  132. package/scripts/lib/context.mjs +67 -0
  133. package/scripts/lib/cross-review-utils.mjs +59 -0
  134. package/scripts/lib/doctor-env-checks.mjs +121 -0
  135. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  136. package/scripts/lib/env-probe.mjs +269 -0
  137. package/scripts/lib/env.mjs +14 -0
  138. package/scripts/lib/gemini-profiles.mjs +119 -0
  139. package/scripts/lib/handoff.mjs +187 -0
  140. package/scripts/lib/hook-utils.mjs +16 -0
  141. package/scripts/lib/hub.mjs +48 -0
  142. package/scripts/lib/keyword-rules.mjs +195 -0
  143. package/scripts/lib/logger.mjs +105 -0
  144. package/scripts/lib/mcp-filter.mjs +883 -0
  145. package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
  146. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  147. package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
  148. package/scripts/lib/mcp-guard-engine.mjs +2120 -0
  149. package/scripts/lib/mcp-health.mjs +773 -0
  150. package/scripts/lib/mcp-manifest.mjs +89 -0
  151. package/scripts/lib/mcp-server-catalog.mjs +355 -0
  152. package/scripts/lib/pid.mjs +63 -0
  153. package/scripts/lib/process-utils.mjs +26 -0
  154. package/scripts/lib/psmux-info.mjs +224 -0
  155. package/scripts/lib/quota.mjs +47 -0
  156. package/scripts/lib/remote-spawn-transfer.mjs +226 -0
  157. package/scripts/lib/skill-state.mjs +220 -0
  158. package/scripts/lib/skill-template.mjs +375 -0
  159. package/scripts/lib/team.mjs +75 -0
  160. package/scripts/lib/timeout.mjs +66 -0
  161. package/scripts/lib/tmp.mjs +26 -0
  162. package/scripts/lib/toml.mjs +113 -0
@@ -1,105 +1,104 @@
1
1
  #!/usr/bin/env node
2
2
  // hub/workers/delegator-mcp.mjs — triflux 위임용 MCP stdio 서버
3
3
 
4
- import { spawn } from 'node:child_process';
5
- import { randomUUID } from 'node:crypto';
6
- import { existsSync, readFileSync } from 'node:fs';
7
- import { dirname, isAbsolute, resolve } from 'node:path';
8
- import process from 'node:process';
9
- import { fileURLToPath, pathToFileURL } from 'node:url';
10
-
11
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
13
- import * as z from 'zod';
14
-
15
- import { CodexMcpWorker } from './codex-mcp.mjs';
16
- import { GeminiWorker } from './gemini-worker.mjs';
4
+ import { spawn } from "node:child_process";
5
+ import { randomUUID } from "node:crypto";
6
+ import { existsSync, readFileSync } from "node:fs";
7
+ import { dirname, isAbsolute, resolve } from "node:path";
8
+ import process from "node:process";
9
+ import { fileURLToPath, pathToFileURL } from "node:url";
10
+
11
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ import * as z from "zod";
14
+ import { resolveBashExecutable } from "@triflux/core/hub/lib/bash-path.mjs";
15
+ import { runHeadlessWithCleanup } from "../team/headless.mjs";
16
+ import { buildWorkerSandboxEnv } from "../team/worker-sandbox.mjs";
17
+ import { CodexMcpWorker } from "./codex-mcp.mjs";
17
18
 
18
19
  const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
19
20
 
20
21
  // mcp-filter.mjs 동적 해석 — 프로젝트(hub/workers/)와 배포(scripts/hub/workers/) 양쪽 대응
21
22
  const MCP_FILTER_CANDIDATES = [
22
- resolve(SCRIPT_DIR, '../../scripts/lib/mcp-filter.mjs'), // 프로젝트 원본
23
- resolve(SCRIPT_DIR, '../../lib/mcp-filter.mjs'), // 배포 (~/.claude/scripts/)
23
+ resolve(SCRIPT_DIR, "../../scripts/lib/mcp-filter.mjs"), // 프로젝트 원본
24
+ resolve(SCRIPT_DIR, "../../lib/mcp-filter.mjs"), // 배포 (~/.claude/scripts/)
24
25
  ];
25
26
  const mcpFilterPath = MCP_FILTER_CANDIDATES.find((p) => existsSync(p));
26
27
  if (!mcpFilterPath) {
27
- throw new Error(`mcp-filter.mjs not found. candidates: ${MCP_FILTER_CANDIDATES.join(', ')}`);
28
+ throw new Error(
29
+ `mcp-filter.mjs not found. candidates: ${MCP_FILTER_CANDIDATES.join(", ")}`,
30
+ );
28
31
  }
29
32
  const {
30
33
  buildPromptHint,
31
34
  getCodexMcpConfig,
32
- getGeminiAllowedServers,
33
35
  SUPPORTED_MCP_PROFILES,
34
36
  } = await import(pathToFileURL(mcpFilterPath).href);
35
- const SERVER_INFO = { name: 'triflux-delegator', version: '1.0.0' };
37
+ const SERVER_INFO = { name: "triflux-delegator", version: "1.0.0" };
36
38
  const DEFAULT_CONTEXT_BYTES = 32 * 1024;
37
39
  const DEFAULT_ROUTE_TIMEOUT_SEC = 120;
38
40
  const DIRECT_PROGRESS_START = 5;
39
41
  const DIRECT_PROGRESS_RUNNING = 60;
40
42
  const DIRECT_PROGRESS_DONE = 100;
41
- const SEARCH_ENGINE_CACHE_PATH = ['.omc', 'state', 'search-engines.json'];
43
+ const SEARCH_ENGINE_CACHE_PATH = [".omc", "state", "search-engines.json"];
42
44
 
43
45
  const AGENT_TIMEOUT_SEC = Object.freeze({
44
46
  executor: 1080,
45
- 'build-fixer': 540,
47
+ "build-fixer": 540,
46
48
  debugger: 900,
47
- 'deep-executor': 3600,
49
+ "deep-executor": 3600,
48
50
  architect: 3600,
49
51
  planner: 3600,
50
52
  critic: 3600,
51
53
  analyst: 3600,
52
- 'code-reviewer': 1800,
53
- 'security-reviewer': 1800,
54
- 'quality-reviewer': 1800,
54
+ "code-reviewer": 1800,
55
+ "security-reviewer": 1800,
56
+ "quality-reviewer": 1800,
55
57
  scientist: 1440,
56
- 'scientist-deep': 3600,
57
- 'document-specialist': 1440,
58
+ "scientist-deep": 3600,
59
+ "document-specialist": 1440,
58
60
  designer: 900,
59
61
  writer: 900,
60
62
  explore: 300,
61
63
  verifier: 1200,
62
- 'test-engineer': 300,
63
- 'qa-tester': 300,
64
+ "test-engineer": 300,
65
+ "qa-tester": 300,
64
66
  spark: 180,
65
67
  });
66
68
 
67
69
  const CODEX_PROFILE_BY_AGENT = Object.freeze({
68
- executor: 'codex53_high',
69
- 'build-fixer': 'codex53_low',
70
- debugger: 'codex53_high',
71
- 'deep-executor': 'gpt54_xhigh',
72
- architect: 'gpt54_xhigh',
73
- planner: 'gpt54_xhigh',
74
- critic: 'gpt54_xhigh',
75
- analyst: 'gpt54_xhigh',
76
- 'code-reviewer': 'codex53_high',
77
- 'security-reviewer': 'codex53_high',
78
- 'quality-reviewer': 'codex53_high',
79
- scientist: 'codex53_high',
80
- 'scientist-deep': 'gpt54_high',
81
- 'document-specialist': 'codex53_high',
82
- verifier: 'codex53_high',
83
- designer: 'gpt54_xhigh', // Gemini primary, codex fallback — UI/UX는 5.4 에이전틱
84
- writer: 'codex53_high', // Gemini primary, codex fallback용
85
- spark: 'spark53_low',
86
- });
87
-
88
- const GEMINI_MODEL_BY_AGENT = Object.freeze({
89
- 'build-fixer': 'gemini-3-flash-preview',
90
- writer: 'gemini-3-flash-preview',
91
- spark: 'gemini-3-flash-preview',
70
+ executor: "gpt55_high",
71
+ "build-fixer": "gpt55_low",
72
+ debugger: "gpt55_xhigh",
73
+ "deep-executor": "gpt55_xhigh",
74
+ architect: "gpt55_xhigh",
75
+ planner: "gpt55_xhigh",
76
+ critic: "gpt55_xhigh",
77
+ analyst: "gpt55_xhigh",
78
+ "code-reviewer": "gpt55_high",
79
+ "security-reviewer": "gpt55_xhigh",
80
+ "quality-reviewer": "gpt55_high",
81
+ scientist: "gpt55_high",
82
+ "scientist-deep": "gpt55_xhigh",
83
+ "document-specialist": "gpt55_high",
84
+ verifier: "gpt55_high",
85
+ designer: "gpt55_xhigh",
86
+ writer: "gpt55_low",
87
+ spark: "gpt55_low",
92
88
  });
93
89
 
94
90
  const REVIEW_INSTRUCTION_BY_AGENT = Object.freeze({
95
- 'code-reviewer': '코드 리뷰 모드로 동작하라. 버그, 리스크, 회귀, 테스트 누락을 우선 식별하라.',
96
- 'security-reviewer': '보안 리뷰 모드로 동작하라. 취약점, 권한 경계, 비밀정보 노출 가능성을 우선 식별하라.',
97
- 'quality-reviewer': '품질 리뷰 모드로 동작하라. 로직 결함, 유지보수성 저하, 테스트 누락을 우선 식별하라.',
91
+ "code-reviewer":
92
+ "코드 리뷰 모드로 동작하라. 버그, 리스크, 회귀, 테스트 누락을 우선 식별하라.",
93
+ "security-reviewer":
94
+ "보안 리뷰 모드로 동작하라. 취약점, 권한 경계, 비밀정보 노출 가능성을 우선 식별하라.",
95
+ "quality-reviewer":
96
+ "품질 리뷰 모드로 동작하라. 로직 결함, 유지보수성 저하, 테스트 누락을 우선 식별하라.",
98
97
  });
99
98
 
100
99
  function cloneEnv(env = process.env) {
101
100
  return Object.fromEntries(
102
- Object.entries(env).filter(([, value]) => typeof value === 'string'),
101
+ Object.entries(env).filter(([, value]) => typeof value === "string"),
103
102
  );
104
103
  }
105
104
 
@@ -108,7 +107,7 @@ function parseJsonArray(raw, fallback = []) {
108
107
  try {
109
108
  const parsed = JSON.parse(raw);
110
109
  return Array.isArray(parsed)
111
- ? parsed.map((item) => String(item ?? '')).filter(Boolean)
110
+ ? parsed.map((item) => String(item ?? "")).filter(Boolean)
112
111
  : [...fallback];
113
112
  } catch {
114
113
  return [...fallback];
@@ -117,7 +116,9 @@ function parseJsonArray(raw, fallback = []) {
117
116
 
118
117
  function resolveCandidatePath(candidate, cwd = process.cwd()) {
119
118
  if (!candidate) return null;
120
- const normalized = isAbsolute(candidate) ? candidate : resolve(cwd, candidate);
119
+ const normalized = isAbsolute(candidate)
120
+ ? candidate
121
+ : resolve(cwd, candidate);
121
122
  return existsSync(normalized) ? normalized : null;
122
123
  }
123
124
 
@@ -126,8 +127,8 @@ function resolveRouteScript(explicitPath, cwd = process.cwd()) {
126
127
  explicitPath,
127
128
  process.env.TFX_DELEGATOR_ROUTE_SCRIPT,
128
129
  process.env.TFX_ROUTE_SCRIPT,
129
- resolve(SCRIPT_DIR, '..', '..', 'scripts', 'tfx-route.sh'),
130
- resolve(cwd, 'scripts', 'tfx-route.sh'),
130
+ resolve(SCRIPT_DIR, "..", "..", "scripts", "tfx-route.sh"),
131
+ resolve(cwd, "scripts", "tfx-route.sh"),
131
132
  ];
132
133
 
133
134
  for (const candidate of candidates) {
@@ -139,11 +140,16 @@ function resolveRouteScript(explicitPath, cwd = process.cwd()) {
139
140
  }
140
141
 
141
142
  function resolveCodexProfile(agentType) {
142
- return CODEX_PROFILE_BY_AGENT[agentType] || 'high';
143
+ return CODEX_PROFILE_BY_AGENT[agentType] || "high";
144
+ }
145
+
146
+ function normalizeProvider(provider = "auto") {
147
+ const value = provider || "auto";
148
+ return value === "gemini" ? "antigravity" : value;
143
149
  }
144
150
 
145
- function resolveGeminiModel(agentType) {
146
- return GEMINI_MODEL_BY_AGENT[agentType] || 'gemini-3.1-pro-preview';
151
+ function normalizeDelegateArgs(args = {}) {
152
+ return { ...args, provider: normalizeProvider(args.provider) };
147
153
  }
148
154
 
149
155
  function resolveTimeoutMs(agentType, timeoutMs) {
@@ -161,13 +167,13 @@ function resolveTimeoutSec(agentType, timeoutMs) {
161
167
  }
162
168
 
163
169
  function loadContextFromFile(contextFile) {
164
- if (!contextFile) return '';
170
+ if (!contextFile) return "";
165
171
  const resolved = resolveCandidatePath(contextFile);
166
- if (!resolved) return '';
172
+ if (!resolved) return "";
167
173
  try {
168
- return readFileSync(resolved, 'utf8').slice(0, DEFAULT_CONTEXT_BYTES);
174
+ return readFileSync(resolved, "utf8").slice(0, DEFAULT_CONTEXT_BYTES);
169
175
  } catch {
170
- return '';
176
+ return "";
171
177
  }
172
178
  }
173
179
 
@@ -177,23 +183,10 @@ function withContext(prompt, contextFile) {
177
183
  return `${prompt}\n\n<prior_context>\n${context}\n</prior_context>`;
178
184
  }
179
185
 
180
- function withPromptHint(prompt, args) {
181
- const promptWithContext = withContext(prompt, args.contextFile);
182
- const hint = buildPromptHint({
183
- agentType: args.agentType,
184
- requestedProfile: args.mcpProfile,
185
- searchTool: args.searchTool,
186
- workerIndex: Number.isInteger(args.workerIndex) ? args.workerIndex : undefined,
187
- taskText: promptWithContext,
188
- });
189
- if (!hint) return promptWithContext;
190
- return `${promptWithContext}. ${hint}`;
191
- }
192
-
193
186
  function joinInstructions(...values) {
194
187
  return values
195
- .filter((value) => typeof value === 'string' && value.trim())
196
- .join('\n')
188
+ .filter((value) => typeof value === "string" && value.trim())
189
+ .join("\n")
197
190
  .trim();
198
191
  }
199
192
 
@@ -202,36 +195,40 @@ function _loadAvailableServersFromSearchEngineCache(cwd = process.cwd()) {
202
195
  if (!existsSync(cacheFile)) return null;
203
196
 
204
197
  try {
205
- const parsed = JSON.parse(readFileSync(cacheFile, 'utf8'));
198
+ const parsed = JSON.parse(readFileSync(cacheFile, "utf8"));
206
199
  if (!Array.isArray(parsed?.engines)) return null;
207
200
  return parsed.engines
208
- .filter((engine) => engine?.status === 'available')
209
- .map((engine) => (typeof engine?.name === 'string' ? engine.name.trim() : ''))
201
+ .filter((engine) => engine?.status === "available")
202
+ .map((engine) =>
203
+ typeof engine?.name === "string" ? engine.name.trim() : "",
204
+ )
210
205
  .filter(Boolean);
211
206
  } catch {
212
207
  return null;
213
208
  }
214
209
  }
215
210
 
216
- function parseRouteType(stderr = '') {
211
+ function parseRouteType(stderr = "") {
217
212
  const match = stderr.match(/type=([a-z-]+)/);
218
213
  if (!match) return null;
219
- if (match[1] === 'codex') return 'codex';
220
- if (match[1] === 'gemini') return 'gemini';
214
+ if (match[1] === "codex") return "codex";
215
+ if (match[1] === "gemini") return "antigravity";
221
216
  return match[1];
222
217
  }
223
218
 
224
219
  function summarizePayload(payload) {
225
- if (typeof payload.output === 'string' && payload.output.trim()) return payload.output.trim();
226
- if (payload.mode === 'async' && payload.job_id) return `비동기 위임이 시작되었습니다. jobId=${payload.job_id}`;
220
+ if (typeof payload.output === "string" && payload.output.trim())
221
+ return payload.output.trim();
222
+ if (payload.mode === "async" && payload.job_id)
223
+ return `비동기 위임이 시작되었습니다. jobId=${payload.job_id}`;
227
224
  if (payload.job_id) return `jobId=${payload.job_id} 상태=${payload.status}`;
228
225
  if (payload.status) return `상태=${payload.status}`;
229
- return payload.ok ? '완료되었습니다.' : '실패했습니다.';
226
+ return payload.ok ? "완료되었습니다." : "실패했습니다.";
230
227
  }
231
228
 
232
229
  function createToolResponse(payload, { isError = false } = {}) {
233
230
  return {
234
- content: [{ type: 'text', text: summarizePayload(payload) }],
231
+ content: [{ type: "text", text: summarizePayload(payload) }],
235
232
  structuredContent: payload,
236
233
  isError,
237
234
  };
@@ -240,55 +237,109 @@ function createToolResponse(payload, { isError = false } = {}) {
240
237
  function createErrorPayload(message, extras = {}) {
241
238
  return {
242
239
  ok: false,
243
- status: 'failed',
240
+ status: "failed",
244
241
  error: message,
245
242
  ...extras,
246
243
  };
247
244
  }
248
245
 
249
246
  const DelegateInputSchema = z.object({
250
- prompt: z.string().min(1).describe('위임할 프롬프트'),
251
- provider: z.enum(['auto', 'codex', 'gemini']).default('auto').describe('사용할 provider'),
252
- mode: z.enum(['sync', 'async']).default('sync').describe('동기 또는 비동기 실행'),
253
- agentType: z.string().default('executor').describe('tfx-route 역할명 또는 direct 실행 역할'),
254
- cwd: z.string().optional().describe('작업 디렉터리'),
255
- timeoutMs: z.number().int().positive().optional().describe('요청 타임아웃(ms)'),
256
- sessionKey: z.string().optional().describe('Codex warm session 재사용 키'),
257
- resetSession: z.boolean().optional().describe('기존 Codex 세션 초기화 여부'),
258
- mcpProfile: z.enum(SUPPORTED_MCP_PROFILES).default('auto'),
259
- contextFile: z.string().optional().describe('tfx-route prior_context 파일 경로'),
260
- availableServers: z.array(z.string()).optional().describe('Codex에 등록된 MCP 서버 이름 목록 (config override 대상)'),
261
- searchTool: z.enum(['brave-search', 'tavily', 'exa']).optional().describe('검색 우선 도구'),
262
- workerIndex: z.number().int().positive().optional().describe('병렬 워커 인덱스'),
263
- model: z.string().optional().describe('직접 실행 시 모델 오버라이드'),
264
- developerInstructions: z.string().optional().describe('직접 실행 시 추가 개발자 지침'),
265
- compactPrompt: z.string().optional().describe('Codex compact prompt'),
266
- threadId: z.string().optional().describe('Codex 직접 실행 시 기존 threadId'),
267
- codexTransport: z.enum(['auto', 'mcp', 'exec']).optional().describe('route 경로용 Codex transport'),
268
- noClaudeNative: z.boolean().optional().describe('route 경로용 TFX_NO_CLAUDE_NATIVE'),
269
- teamName: z.string().optional().describe('TFX_TEAM_NAME'),
270
- teamTaskId: z.string().optional().describe('TFX_TEAM_TASK_ID'),
271
- teamAgentName: z.string().optional().describe('TFX_TEAM_AGENT_NAME'),
272
- teamLeadName: z.string().optional().describe('TFX_TEAM_LEAD_NAME'),
273
- hubUrl: z.string().optional().describe('TFX_HUB_URL'),
247
+ prompt: z.string().min(1).describe("위임할 프롬프트"),
248
+ provider: z
249
+ .enum(["auto", "codex", "antigravity", "gemini"])
250
+ .default("auto")
251
+ .describe("사용할 provider"),
252
+ mode: z
253
+ .enum(["sync", "async"])
254
+ .default("sync")
255
+ .describe("동기 또는 비동기 실행"),
256
+ agentType: z
257
+ .string()
258
+ .default("executor")
259
+ .describe("tfx-route 역할명 또는 direct 실행 역할"),
260
+ cwd: z.string().optional().describe("작업 디렉터리"),
261
+ timeoutMs: z
262
+ .number()
263
+ .int()
264
+ .positive()
265
+ .optional()
266
+ .describe("요청 타임아웃(ms)"),
267
+ sessionKey: z.string().optional().describe("Codex warm session 재사용 키"),
268
+ resetSession: z.boolean().optional().describe("기존 Codex 세션 초기화 여부"),
269
+ mcpProfile: z.enum(SUPPORTED_MCP_PROFILES).default("auto"),
270
+ contextFile: z
271
+ .string()
272
+ .optional()
273
+ .describe("tfx-route prior_context 파일 경로"),
274
+ availableServers: z
275
+ .array(z.string())
276
+ .optional()
277
+ .describe("Codex에 등록된 MCP 서버 이름 목록 (config override 대상)"),
278
+ searchTool: z
279
+ .enum(["brave-search", "tavily", "exa"])
280
+ .optional()
281
+ .describe("검색 우선 도구"),
282
+ workerIndex: z
283
+ .number()
284
+ .int()
285
+ .positive()
286
+ .optional()
287
+ .describe("병렬 워커 인덱스"),
288
+ model: z.string().optional().describe("직접 실행 시 모델 오버라이드"),
289
+ developerInstructions: z
290
+ .string()
291
+ .optional()
292
+ .describe("직접 실행 시 추가 개발자 지침"),
293
+ compactPrompt: z.string().optional().describe("Codex compact prompt"),
294
+ threadId: z.string().optional().describe("Codex 직접 실행 시 기존 threadId"),
295
+ codexTransport: z
296
+ .enum(["auto", "mcp", "exec"])
297
+ .optional()
298
+ .describe("route 경로용 Codex transport"),
299
+ noClaudeNative: z
300
+ .boolean()
301
+ .optional()
302
+ .describe("route 경로용 TFX_NO_CLAUDE_NATIVE"),
303
+ teamName: z.string().optional().describe("TFX_TEAM_NAME"),
304
+ teamTaskId: z.string().optional().describe("TFX_TEAM_TASK_ID"),
305
+ teamAgentName: z.string().optional().describe("TFX_TEAM_AGENT_NAME"),
306
+ teamLeadName: z.string().optional().describe("TFX_TEAM_LEAD_NAME"),
307
+ hubUrl: z.string().optional().describe("TFX_HUB_URL"),
308
+ workers: z
309
+ .array(
310
+ z.object({
311
+ provider: z
312
+ .enum(["codex", "antigravity", "gemini"])
313
+ .describe("워커 provider"),
314
+ agentType: z.string().default("executor").describe("역할명"),
315
+ prompt: z.string().describe("워커별 프롬프트"),
316
+ mcpProfile: z.string().default("auto").describe("MCP 프로필"),
317
+ model: z.string().optional().describe("모델 오버라이드"),
318
+ }),
319
+ )
320
+ .optional()
321
+ .describe("병렬 멀티워커 목록. 지정 시 psmux 기반 병렬 실행"),
274
322
  });
275
323
 
276
324
  const DelegateStatusInputSchema = z.object({
277
- jobId: z.string().min(1).describe('조회할 비동기 job ID'),
325
+ jobId: z.string().min(1).describe("조회할 비동기 job ID"),
278
326
  });
279
327
 
280
328
  const DelegateReplyInputSchema = z.object({
281
- job_id: z.string().min(1).describe('후속 응답을 보낼 기존 delegate job ID'),
282
- reply: z.string().min(1).describe('후속 사용자 응답'),
283
- done: z.boolean().default(false).describe('true이면 응답 처리 후 대화를 종료'),
329
+ job_id: z.string().min(1).describe("후속 응답을 보낼 기존 delegate job ID"),
330
+ reply: z.string().min(1).describe("후속 사용자 응답"),
331
+ done: z
332
+ .boolean()
333
+ .default(false)
334
+ .describe("true이면 응답 처리 후 대화를 종료"),
284
335
  });
285
336
 
286
337
  const DelegateOutputSchema = z.object({
287
338
  ok: z.boolean(),
288
339
  jobId: z.string().optional(),
289
340
  job_id: z.string().optional(),
290
- mode: z.enum(['sync', 'async']).optional(),
291
- status: z.enum(['running', 'completed', 'failed']).optional(),
341
+ mode: z.enum(["sync", "async"]).optional(),
342
+ status: z.enum(["running", "completed", "failed", "partial"]).optional(),
292
343
  error: z.string().optional(),
293
344
  providerRequested: z.string().optional(),
294
345
  providerResolved: z.string().nullable().optional(),
@@ -304,33 +355,52 @@ const DelegateOutputSchema = z.object({
304
355
  threadId: z.string().nullable().optional(),
305
356
  sessionKey: z.string().nullable().optional(),
306
357
  conversationOpen: z.boolean().optional(),
358
+ workerResults: z
359
+ .array(
360
+ z.object({
361
+ cli: z.string(),
362
+ role: z.string().optional(),
363
+ paneName: z.string(),
364
+ matched: z.boolean(),
365
+ exitCode: z.number().nullable(),
366
+ output: z.string(),
367
+ }),
368
+ )
369
+ .optional()
370
+ .describe("멀티워커 개별 결과"),
371
+ sessionName: z.string().optional().describe("psmux 세션명"),
307
372
  });
308
373
 
309
374
  function isTeamRouteRequested(args) {
310
375
  return Boolean(
311
- args.teamName
312
- || args.teamTaskId
313
- || args.teamAgentName
314
- || args.teamLeadName
315
- || args.hubUrl
376
+ args.teamName ||
377
+ args.teamTaskId ||
378
+ args.teamAgentName ||
379
+ args.teamLeadName ||
380
+ args.hubUrl,
316
381
  );
317
382
  }
318
383
 
319
384
  function pickRouteMode(provider) {
320
- return provider === 'auto' ? 'auto' : provider;
385
+ const normalized = normalizeProvider(provider);
386
+ return normalized === "auto" ? "auto" : normalized;
321
387
  }
322
388
 
323
389
  function sanitizeDelegateArgs(args = {}) {
324
390
  return {
325
- provider: args.provider || 'auto',
326
- agentType: args.agentType || 'executor',
391
+ provider: normalizeProvider(args.provider),
392
+ agentType: args.agentType || "executor",
327
393
  cwd: args.cwd || null,
328
- timeoutMs: Number.isFinite(Number(args.timeoutMs)) ? Math.trunc(Number(args.timeoutMs)) : null,
394
+ timeoutMs: Number.isFinite(Number(args.timeoutMs))
395
+ ? Math.trunc(Number(args.timeoutMs))
396
+ : null,
329
397
  sessionKey: args.sessionKey || null,
330
398
  resetSession: Boolean(args.resetSession),
331
- mcpProfile: args.mcpProfile || 'auto',
399
+ mcpProfile: args.mcpProfile || "auto",
332
400
  contextFile: args.contextFile || null,
333
- availableServers: Array.isArray(args.availableServers) ? args.availableServers : null,
401
+ availableServers: Array.isArray(args.availableServers)
402
+ ? args.availableServers
403
+ : null,
334
404
  searchTool: args.searchTool || null,
335
405
  workerIndex: Number.isInteger(args.workerIndex) ? args.workerIndex : null,
336
406
  model: args.model || null,
@@ -347,22 +417,10 @@ function sanitizeDelegateArgs(args = {}) {
347
417
  };
348
418
  }
349
419
 
350
- function formatConversationTranscript(turns = []) {
351
- return turns.map((turn, index) => {
352
- const parts = [
353
- `Turn ${index + 1} user:\n${turn.user}`,
354
- ];
355
- if (typeof turn.assistant === 'string' && turn.assistant.trim()) {
356
- parts.push(`Turn ${index + 1} assistant:\n${turn.assistant}`);
357
- }
358
- return parts.join('\n\n');
359
- }).join('\n\n');
360
- }
361
-
362
420
  async function emitProgress(extra, progress, total, message) {
363
421
  if (extra?._meta?.progressToken === undefined) return;
364
422
  await extra.sendNotification({
365
- method: 'notifications/progress',
423
+ method: "notifications/progress",
366
424
  params: {
367
425
  progressToken: extra._meta.progressToken,
368
426
  progress,
@@ -373,39 +431,36 @@ async function emitProgress(extra, progress, total, message) {
373
431
  }
374
432
 
375
433
  export class DelegatorMcpWorker {
376
- type = 'delegator';
434
+ type = "delegator";
377
435
 
378
436
  constructor(options = {}) {
379
437
  this.cwd = options.cwd || process.cwd();
380
438
  this.env = cloneEnv({ ...cloneEnv(process.env), ...cloneEnv(options.env) });
381
439
  this.routeScript = resolveRouteScript(options.routeScript, this.cwd);
382
- this.bashCommand = options.bashCommand
383
- || this.env.TFX_DELEGATOR_BASH_COMMAND
384
- || this.env.BASH_BIN
385
- || 'bash';
440
+ this.bashCommand =
441
+ options.bashCommand ||
442
+ this.env.TFX_DELEGATOR_BASH_COMMAND ||
443
+ this.env.BASH_BIN ||
444
+ resolveBashExecutable();
386
445
 
387
446
  this.codexWorker = new CodexMcpWorker({
388
- command: options.codexCommand
389
- || this.env.TFX_DELEGATOR_CODEX_COMMAND
390
- || this.env.CODEX_BIN
391
- || 'codex',
392
- args: Array.isArray(options.codexArgs) && options.codexArgs.length
393
- ? options.codexArgs
394
- : parseJsonArray(this.env.TFX_DELEGATOR_CODEX_ARGS_JSON, []),
447
+ command:
448
+ options.codexCommand ||
449
+ this.env.TFX_DELEGATOR_CODEX_COMMAND ||
450
+ this.env.CODEX_BIN ||
451
+ "codex",
452
+ args:
453
+ Array.isArray(options.codexArgs) && options.codexArgs.length
454
+ ? options.codexArgs
455
+ : parseJsonArray(this.env.TFX_DELEGATOR_CODEX_ARGS_JSON, []),
395
456
  cwd: this.cwd,
396
457
  env: this.env,
397
458
  clientInfo: { name: SERVER_INFO.name, version: SERVER_INFO.version },
398
459
  });
399
460
 
400
- this.geminiCommand = options.geminiCommand || this.env.GEMINI_BIN || 'gemini';
401
- this.geminiCommandArgs = Array.isArray(options.geminiArgs) && options.geminiArgs.length
402
- ? [...options.geminiArgs]
403
- : parseJsonArray(this.env.GEMINI_BIN_ARGS_JSON, []);
404
-
405
461
  this.server = null;
406
462
  this.transport = null;
407
463
  this.jobs = new Map();
408
- this.geminiConversations = new Map();
409
464
  this.routeChildren = new Set();
410
465
  this.ready = false;
411
466
  }
@@ -424,32 +479,48 @@ export class DelegatorMcpWorker {
424
479
  capabilities: { logging: {} },
425
480
  });
426
481
 
427
- server.registerTool('triflux-delegate', {
428
- description: '새 위임을 실행합니다. codex/gemini direct 경로와 tfx-route 기반 auto 라우팅을 모두 지원합니다.',
429
- inputSchema: DelegateInputSchema,
430
- outputSchema: DelegateOutputSchema,
431
- }, async (args, extra) => {
432
- const payload = await this.delegate(args, extra);
433
- return createToolResponse(payload, { isError: payload.ok === false && payload.mode !== 'async' });
434
- });
435
-
436
- server.registerTool('triflux-delegate-status', {
437
- description: '비동기 위임 job 상태를 조회합니다.',
438
- inputSchema: DelegateStatusInputSchema,
439
- outputSchema: DelegateOutputSchema,
440
- }, async ({ jobId }, extra) => {
441
- const payload = await this.getJobStatus(jobId, extra);
442
- return createToolResponse(payload, { isError: payload.ok === false });
443
- });
444
-
445
- server.registerTool('triflux-delegate-reply', {
446
- description: '기존 delegate job에 후속 응답을 보내고, Gemini direct job이면 multi-turn 대화를 이어갑니다.',
447
- inputSchema: DelegateReplyInputSchema,
448
- outputSchema: DelegateOutputSchema,
449
- }, async (args, extra) => {
450
- const payload = await this.reply(args, extra);
451
- return createToolResponse(payload, { isError: payload.ok === false });
452
- });
482
+ server.registerTool(
483
+ "triflux-delegate",
484
+ {
485
+ description:
486
+ "새 위임을 실행합니다. codex direct 경로와 antigravity/tfx-route 기반 auto 라우팅을 지원합니다.",
487
+ inputSchema: DelegateInputSchema,
488
+ outputSchema: DelegateOutputSchema,
489
+ },
490
+ async (args, extra) => {
491
+ const payload = await this.delegate(args, extra);
492
+ return createToolResponse(payload, {
493
+ isError: payload.ok === false && payload.mode !== "async",
494
+ });
495
+ },
496
+ );
497
+
498
+ server.registerTool(
499
+ "triflux-delegate-status",
500
+ {
501
+ description: "비동기 위임 job 상태를 조회합니다.",
502
+ inputSchema: DelegateStatusInputSchema,
503
+ outputSchema: DelegateOutputSchema,
504
+ },
505
+ async ({ jobId }, extra) => {
506
+ const payload = await this.getJobStatus(jobId, extra);
507
+ return createToolResponse(payload, { isError: payload.ok === false });
508
+ },
509
+ );
510
+
511
+ server.registerTool(
512
+ "triflux-delegate-reply",
513
+ {
514
+ description:
515
+ "기존 delegate job에 후속 응답을 보냅니다. 현재 route 기반 Antigravity job은 후속 응답을 지원하지 않습니다.",
516
+ inputSchema: DelegateReplyInputSchema,
517
+ outputSchema: DelegateOutputSchema,
518
+ },
519
+ async (args, extra) => {
520
+ const payload = await this.reply(args, extra);
521
+ return createToolResponse(payload, { isError: payload.ok === false });
522
+ },
523
+ );
453
524
 
454
525
  this.server = server;
455
526
  this.ready = true;
@@ -467,20 +538,13 @@ export class DelegatorMcpWorker {
467
538
  this.ready = false;
468
539
 
469
540
  for (const child of this.routeChildren) {
470
- try { child.kill(); } catch {}
541
+ try {
542
+ child.kill();
543
+ } catch {}
471
544
  }
472
545
  this.routeChildren.clear();
473
546
 
474
547
  await this.codexWorker.stop().catch(() => {});
475
-
476
- for (const job of this.jobs.values()) {
477
- if (job.worker) {
478
- await job.worker.stop().catch(() => {});
479
- job.worker = null;
480
- }
481
- }
482
- this.geminiConversations.clear();
483
-
484
548
  if (this.server) {
485
549
  await this.server.close().catch(() => {});
486
550
  }
@@ -490,13 +554,15 @@ export class DelegatorMcpWorker {
490
554
  }
491
555
 
492
556
  async run(prompt, options = {}) {
493
- return this._executeDirect({ prompt, ...options });
557
+ return this._executeDirect(normalizeDelegateArgs({ prompt, ...options }));
494
558
  }
495
559
 
496
560
  async execute(prompt, options = {}) {
497
- const result = await this._executeDirect({ prompt, ...options });
561
+ const result = await this._executeDirect(
562
+ normalizeDelegateArgs({ prompt, ...options }),
563
+ );
498
564
  return {
499
- output: result.output || result.error || '',
565
+ output: result.output || result.error || "",
500
566
  exitCode: result.exitCode ?? (result.ok ? 0 : 1),
501
567
  threadId: result.threadId || null,
502
568
  sessionKey: result.sessionKey || null,
@@ -505,10 +571,11 @@ export class DelegatorMcpWorker {
505
571
  }
506
572
 
507
573
  async delegate(args, extra) {
508
- if (args.mode === 'async') {
509
- return this._startAsyncJob(args, extra);
574
+ const normalizedArgs = normalizeDelegateArgs(args);
575
+ if (normalizedArgs.mode === "async") {
576
+ return this._startAsyncJob(normalizedArgs, extra);
510
577
  }
511
- return this._runSyncJob(args, extra);
578
+ return this._runSyncJob(normalizedArgs, extra);
512
579
  }
513
580
 
514
581
  async getJobStatus(jobId, extra) {
@@ -518,141 +585,56 @@ export class DelegatorMcpWorker {
518
585
  }
519
586
 
520
587
  const payload = this._serializeJob(job);
521
- if (job.status === 'running') {
588
+ if (job.status === "running") {
522
589
  await emitProgress(extra, 25, 100, `job ${jobId} 실행 중`);
523
- } else if (job.status === 'completed') {
590
+ } else if (job.status === "completed") {
524
591
  await emitProgress(extra, DIRECT_PROGRESS_DONE, 100, `job ${jobId} 완료`);
525
- } else if (job.status === 'failed') {
592
+ } else if (job.status === "failed") {
526
593
  await emitProgress(extra, DIRECT_PROGRESS_DONE, 100, `job ${jobId} 실패`);
527
594
  }
528
595
  return payload;
529
596
  }
530
597
 
531
- async reply({ job_id, reply, done = false }, extra) {
598
+ async reply({ job_id }, extra) {
532
599
  const job = this.jobs.get(job_id);
533
600
  if (!job) {
534
- return createErrorPayload(`알 수 없는 jobId: ${job_id}`, { jobId: job_id, job_id });
535
- }
536
- if (job.status === 'running') {
537
- return createErrorPayload(`job ${job_id}가 아직 실행 중입니다.`, { jobId: job_id, job_id });
538
- }
539
- if (job.providerRequested !== 'gemini' || job.transport !== 'gemini-worker') {
540
- return createErrorPayload('delegate-reply는 현재 direct Gemini job에만 지원됩니다.', {
601
+ return createErrorPayload(`알 수 없는 jobId: ${job_id}`, {
541
602
  jobId: job_id,
542
603
  job_id,
543
604
  });
544
605
  }
545
-
546
- const conversation = this.geminiConversations.get(job_id);
547
- if (!conversation) {
548
- return createErrorPayload(`Gemini 대화 컨텍스트가 없습니다: ${job_id}`, { jobId: job_id, job_id });
549
- }
550
- if (conversation.closed) {
551
- return createErrorPayload(`이미 종료된 대화입니다: ${job_id}`, { jobId: job_id, job_id });
552
- }
553
-
554
- await emitProgress(extra, DIRECT_PROGRESS_START, 100, `job ${job_id} 후속 응답을 시작합니다.`);
555
- job.status = 'running';
556
- job.updatedAt = new Date().toISOString();
557
-
558
- const worker = this._createGeminiWorker();
559
- job.worker = worker;
560
- const prompt = this._buildGeminiReplyPrompt(conversation, reply);
561
-
562
- try {
563
- const result = await worker.execute(prompt, {
564
- cwd: job.requestArgs.cwd || this.cwd,
565
- timeoutMs: resolveTimeoutMs(job.agentType, job.requestArgs.timeoutMs),
566
- model: job.requestArgs.model || resolveGeminiModel(job.agentType),
567
- approvalMode: 'yolo',
568
- allowedMcpServerNames: getGeminiAllowedServers(this._getMcpPolicyOptions(job.requestArgs)),
569
- });
570
-
571
- conversation.turns.push({
572
- user: reply,
573
- assistant: result.output,
574
- });
575
- conversation.updatedAt = new Date().toISOString();
576
- conversation.closed = Boolean(done);
577
- if (done) {
578
- this.geminiConversations.delete(job_id);
579
- }
580
-
581
- this._applyJobResult(job, {
582
- ok: result.exitCode === 0,
583
- status: result.exitCode === 0 ? 'completed' : 'failed',
584
- providerRequested: 'gemini',
585
- providerResolved: 'gemini',
586
- agentType: job.agentType,
587
- transport: 'gemini-worker',
588
- exitCode: result.exitCode,
589
- output: result.output,
590
- sessionKey: result.sessionKey || job.sessionKey || null,
606
+ if (job.status === "running") {
607
+ return createErrorPayload(`job ${job_id}가 아직 실행 중입니다.`, {
608
+ jobId: job_id,
609
+ job_id,
591
610
  });
592
- await emitProgress(extra, DIRECT_PROGRESS_DONE, 100, `job ${job_id} 후속 응답이 완료되었습니다.`);
593
- return this._serializeJob(job);
594
- } catch (error) {
595
- const message = error instanceof Error ? error.message : String(error);
596
- this._applyJobResult(job, createErrorPayload(message, {
597
- mode: job.mode,
598
- providerRequested: 'gemini',
599
- providerResolved: 'gemini',
600
- agentType: job.agentType,
601
- transport: 'gemini-worker',
602
- }));
603
- return this._serializeJob(job);
604
- } finally {
605
- await worker.stop().catch(() => {});
606
- job.worker = null;
607
611
  }
608
- }
609
-
610
- _createGeminiWorker() {
611
- return new GeminiWorker({
612
- command: this.geminiCommand,
613
- commandArgs: this.geminiCommandArgs,
614
- cwd: this.cwd,
615
- env: this.env,
616
- });
612
+ void extra;
613
+ return createErrorPayload(
614
+ "delegate-reply는 route 기반 Antigravity job에 지원되지 않습니다.",
615
+ {
616
+ jobId: job_id,
617
+ job_id,
618
+ },
619
+ );
617
620
  }
618
621
 
619
622
  _buildDirectPrompt(args) {
620
- return withContext(String(args.prompt ?? ''), args.contextFile);
621
- }
622
-
623
- _buildDirectPromptWithHint(args) {
624
- return withPromptHint(String(args.prompt ?? ''), {
625
- agentType: args.agentType || 'executor',
626
- mcpProfile: args.mcpProfile || 'auto',
627
- searchTool: args.searchTool,
628
- workerIndex: Number.isInteger(args.workerIndex) ? args.workerIndex : undefined,
629
- contextFile: args.contextFile,
630
- });
631
- }
632
-
633
- _buildGeminiReplyPrompt(conversation, reply) {
634
- const transcript = formatConversationTranscript(conversation.turns);
635
- return [
636
- 'Continue the conversation using the prior transcript below.',
637
- '',
638
- '<conversation_history>',
639
- transcript,
640
- '</conversation_history>',
641
- '',
642
- '<latest_user_reply>',
643
- reply,
644
- '</latest_user_reply>',
645
- ].join('\n');
623
+ return withContext(String(args.prompt ?? ""), args.contextFile);
646
624
  }
647
625
 
648
626
  _getMcpPolicyOptions(args) {
649
627
  return {
650
- agentType: args.agentType || 'executor',
651
- requestedProfile: args.mcpProfile || 'auto',
628
+ agentType: args.agentType || "executor",
629
+ requestedProfile: args.mcpProfile || "auto",
652
630
  searchTool: args.searchTool,
653
- workerIndex: Number.isInteger(args.workerIndex) ? args.workerIndex : undefined,
654
- taskText: withContext(String(args.prompt ?? ''), args.contextFile),
655
- availableServers: Array.isArray(args.availableServers) ? args.availableServers : undefined,
631
+ workerIndex: Number.isInteger(args.workerIndex)
632
+ ? args.workerIndex
633
+ : undefined,
634
+ taskText: withContext(String(args.prompt ?? ""), args.contextFile),
635
+ availableServers: Array.isArray(args.availableServers)
636
+ ? args.availableServers
637
+ : undefined,
656
638
  };
657
639
  }
658
640
 
@@ -661,11 +643,43 @@ export class DelegatorMcpWorker {
661
643
  }
662
644
 
663
645
  _shouldUseRoute(args) {
664
- return args.provider === 'auto' || isTeamRouteRequested(args);
646
+ return (
647
+ args.provider === "auto" ||
648
+ args.provider === "antigravity" ||
649
+ args.provider === "gemini" ||
650
+ isTeamRouteRequested(args)
651
+ );
665
652
  }
666
653
 
667
654
  async _executeDirect(args, extra = null) {
668
- await emitProgress(extra, DIRECT_PROGRESS_START, 100, '위임 실행을 시작합니다.');
655
+ await emitProgress(
656
+ extra,
657
+ DIRECT_PROGRESS_START,
658
+ 100,
659
+ "위임 실행을 시작합니다.",
660
+ );
661
+
662
+ // 멀티워커 분기: workers 배열이 있으면 headless psmux 병렬 실행
663
+ if (Array.isArray(args.workers) && args.workers.length > 0) {
664
+ try {
665
+ const result = await this._executeMultiWorker(args, extra);
666
+ await emitProgress(
667
+ extra,
668
+ DIRECT_PROGRESS_DONE,
669
+ 100,
670
+ "위임이 완료되었습니다.",
671
+ );
672
+ return result;
673
+ } catch (error) {
674
+ const message = error instanceof Error ? error.message : String(error);
675
+ return createErrorPayload(message, {
676
+ mode: "sync",
677
+ providerRequested: "multi",
678
+ agentType: args.agentType,
679
+ transport: "headless-psmux",
680
+ });
681
+ }
682
+ }
669
683
 
670
684
  const runViaRoute = this._shouldUseRoute(args);
671
685
 
@@ -674,50 +688,152 @@ export class DelegatorMcpWorker {
674
688
  ? await this._executeRoute(args, extra)
675
689
  : await this._executeWorker(args, extra);
676
690
 
677
- await emitProgress(extra, DIRECT_PROGRESS_DONE, 100, '위임이 완료되었습니다.');
691
+ await emitProgress(
692
+ extra,
693
+ DIRECT_PROGRESS_DONE,
694
+ 100,
695
+ "위임이 완료되었습니다.",
696
+ );
678
697
  return result;
679
698
  } catch (error) {
680
699
  const message = error instanceof Error ? error.message : String(error);
681
700
  return createErrorPayload(message, {
682
- mode: 'sync',
701
+ mode: "sync",
683
702
  providerRequested: args.provider,
684
703
  agentType: args.agentType,
685
- transport: runViaRoute ? 'route-script' : `${args.provider}-worker`,
704
+ transport: runViaRoute ? "route-script" : `${args.provider}-worker`,
686
705
  });
687
706
  }
688
707
  }
689
708
 
690
- async _executeWorker(args, extra) {
691
- await emitProgress(extra, DIRECT_PROGRESS_RUNNING, 100, '직접 워커 경로로 실행 중입니다.');
692
-
693
- if (args.provider === 'codex') {
694
- const result = await this.codexWorker.execute(this._buildDirectPrompt(args), {
695
- cwd: args.cwd || this.cwd,
696
- timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
697
- sessionKey: args.sessionKey,
698
- threadId: args.threadId,
699
- resetSession: args.resetSession,
700
- profile: resolveCodexProfile(args.agentType),
701
- sandbox: 'danger-full-access',
702
- approvalPolicy: 'never',
703
- developerInstructions: joinInstructions(
704
- REVIEW_INSTRUCTION_BY_AGENT[args.agentType],
705
- this._buildPromptHintInstruction(args),
706
- args.developerInstructions,
709
+ async _executeMultiWorker(args, extra) {
710
+ await emitProgress(
711
+ extra,
712
+ DIRECT_PROGRESS_START,
713
+ 100,
714
+ "멀티워커 psmux 병렬 실행을 시작합니다.",
715
+ );
716
+
717
+ // workers 배열을 headless assignments 형식으로 변환
718
+ const assignments = args.workers.map((w) => {
719
+ // provider를 CLI 이름으로 매핑. gemini는 deprecated alias로 antigravity에 합류한다.
720
+ const cli = normalizeProvider(w.provider);
721
+ const role = w.agentType || "executor";
722
+ // buildDirectPrompt와 동일한 context 처리
723
+ const prompt = withContext(String(w.prompt || ""), args.contextFile);
724
+ return { cli, prompt, role };
725
+ });
726
+
727
+ // 타임아웃: 워커 중 가장 긴 타임아웃 사용
728
+ const maxTimeoutSec = Math.max(
729
+ ...args.workers.map((w) =>
730
+ Math.ceil(
731
+ resolveTimeoutMs(w.agentType || "executor", args.timeoutMs) / 1000,
707
732
  ),
708
- config: getCodexMcpConfig(this._getMcpPolicyOptions(args)),
709
- compactPrompt: args.compactPrompt,
710
- model: args.model,
733
+ ),
734
+ 300,
735
+ );
736
+
737
+ try {
738
+ const { results, sessionName } = await runHeadlessWithCleanup(
739
+ assignments,
740
+ {
741
+ sessionPrefix: "dlg",
742
+ timeoutSec: maxTimeoutSec,
743
+ layout: assignments.length <= 2 ? "even-horizontal" : "2x2",
744
+ progressive: true,
745
+ dashboard: true,
746
+ dashboardLayout: "single",
747
+ },
748
+ );
749
+
750
+ await emitProgress(
751
+ extra,
752
+ DIRECT_PROGRESS_DONE,
753
+ 100,
754
+ `멀티워커 실행 완료: ${results.length}개 워커`,
755
+ );
756
+
757
+ // 전체 성공 판단: 모든 워커가 matched && exitCode === 0
758
+ const allOk = results.every((r) => r.matched && r.exitCode === 0);
759
+ // 개별 출력을 합산
760
+ const combinedOutput = results
761
+ .map(
762
+ (r, i) =>
763
+ `=== Worker ${i + 1} (${r.cli}/${assignments[i].role}) ===\n${r.output || "(no output)"}`,
764
+ )
765
+ .join("\n\n");
766
+
767
+ return {
768
+ ok: allOk,
769
+ mode: "sync",
770
+ status: allOk ? "completed" : "partial",
771
+ providerRequested: "multi",
772
+ providerResolved: "multi",
773
+ agentType: args.agentType || "executor",
774
+ transport: "headless-psmux",
775
+ exitCode: allOk ? 0 : 1,
776
+ output: combinedOutput,
777
+ workerResults: results.map((r) => ({
778
+ cli: r.cli,
779
+ role: r.role || "",
780
+ paneName: r.paneName,
781
+ matched: r.matched,
782
+ exitCode: r.exitCode,
783
+ output: r.output || "",
784
+ })),
785
+ sessionName,
786
+ };
787
+ } catch (error) {
788
+ const message = error instanceof Error ? error.message : String(error);
789
+ return createErrorPayload(message, {
790
+ mode: "sync",
791
+ providerRequested: "multi",
792
+ agentType: args.agentType || "executor",
793
+ transport: "headless-psmux",
711
794
  });
795
+ }
796
+ }
797
+
798
+ async _executeWorker(args, extra) {
799
+ await emitProgress(
800
+ extra,
801
+ DIRECT_PROGRESS_RUNNING,
802
+ 100,
803
+ "직접 워커 경로로 실행 중입니다.",
804
+ );
805
+
806
+ if (args.provider === "codex") {
807
+ const result = await this.codexWorker.execute(
808
+ this._buildDirectPrompt(args),
809
+ {
810
+ cwd: args.cwd || this.cwd,
811
+ timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
812
+ sessionKey: args.sessionKey,
813
+ threadId: args.threadId,
814
+ resetSession: args.resetSession,
815
+ profile: resolveCodexProfile(args.agentType),
816
+ sandbox: "danger-full-access",
817
+ approvalPolicy: "never",
818
+ developerInstructions: joinInstructions(
819
+ REVIEW_INSTRUCTION_BY_AGENT[args.agentType],
820
+ this._buildPromptHintInstruction(args),
821
+ args.developerInstructions,
822
+ ),
823
+ config: getCodexMcpConfig(this._getMcpPolicyOptions(args)),
824
+ compactPrompt: args.compactPrompt,
825
+ model: args.model,
826
+ },
827
+ );
712
828
 
713
829
  return {
714
830
  ok: result.exitCode === 0,
715
- mode: 'sync',
716
- status: result.exitCode === 0 ? 'completed' : 'failed',
717
- providerRequested: 'codex',
718
- providerResolved: 'codex',
831
+ mode: "sync",
832
+ status: result.exitCode === 0 ? "completed" : "failed",
833
+ providerRequested: "codex",
834
+ providerResolved: "codex",
719
835
  agentType: args.agentType,
720
- transport: 'codex-mcp',
836
+ transport: "codex-mcp",
721
837
  exitCode: result.exitCode,
722
838
  output: result.output,
723
839
  sessionKey: result.sessionKey,
@@ -725,64 +841,42 @@ export class DelegatorMcpWorker {
725
841
  };
726
842
  }
727
843
 
728
- if (args.provider === 'gemini') {
729
- const worker = this._createGeminiWorker();
730
- const prompt = this._buildDirectPromptWithHint(args);
731
- try {
732
- const result = await worker.execute(prompt, {
733
- cwd: args.cwd || this.cwd,
734
- timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
735
- model: args.model || resolveGeminiModel(args.agentType),
736
- approvalMode: 'yolo',
737
- allowedMcpServerNames: getGeminiAllowedServers(this._getMcpPolicyOptions(args)),
738
- });
739
-
740
- return {
741
- ok: result.exitCode === 0,
742
- mode: 'sync',
743
- status: result.exitCode === 0 ? 'completed' : 'failed',
744
- providerRequested: 'gemini',
745
- providerResolved: 'gemini',
746
- agentType: args.agentType,
747
- transport: 'gemini-worker',
748
- exitCode: result.exitCode,
749
- output: result.output,
750
- sessionKey: result.sessionKey,
751
- _geminiPrompt: prompt,
752
- };
753
- } finally {
754
- await worker.stop().catch(() => {});
755
- }
756
- }
757
-
758
- return createErrorPayload(`지원하지 않는 direct provider: ${args.provider}`, {
759
- mode: 'sync',
760
- providerRequested: args.provider,
761
- agentType: args.agentType,
762
- transport: 'direct-worker',
763
- });
844
+ return createErrorPayload(
845
+ `지원하지 않는 direct provider: ${args.provider}`,
846
+ {
847
+ mode: "sync",
848
+ providerRequested: args.provider,
849
+ agentType: args.agentType,
850
+ transport: "direct-worker",
851
+ },
852
+ );
764
853
  }
765
854
 
766
855
  async _executeRoute(args, extra) {
767
856
  if (!this.routeScript) {
768
- return createErrorPayload('tfx-route.sh 경로를 찾지 못했습니다.', {
769
- mode: 'sync',
857
+ return createErrorPayload("tfx-route.sh 경로를 찾지 못했습니다.", {
858
+ mode: "sync",
770
859
  providerRequested: args.provider,
771
860
  agentType: args.agentType,
772
- transport: 'route-script',
861
+ transport: "route-script",
773
862
  });
774
863
  }
775
864
 
776
- await emitProgress(extra, DIRECT_PROGRESS_RUNNING, 100, 'tfx-route.sh 경로로 실행 중입니다.');
865
+ await emitProgress(
866
+ extra,
867
+ DIRECT_PROGRESS_RUNNING,
868
+ 100,
869
+ "tfx-route.sh 경로로 실행 중입니다.",
870
+ );
777
871
  const result = await this._spawnRoute(args);
778
872
  return {
779
873
  ok: result.exitCode === 0,
780
- mode: 'sync',
781
- status: result.exitCode === 0 ? 'completed' : 'failed',
874
+ mode: "sync",
875
+ status: result.exitCode === 0 ? "completed" : "failed",
782
876
  providerRequested: args.provider,
783
877
  providerResolved: parseRouteType(result.stderr) || args.provider,
784
878
  agentType: args.agentType,
785
- transport: 'route-script',
879
+ transport: "route-script",
786
880
  exitCode: result.exitCode,
787
881
  output: result.stdout.trim() || result.stderr.trim(),
788
882
  stderr: result.stderr.trim(),
@@ -790,10 +884,15 @@ export class DelegatorMcpWorker {
790
884
  }
791
885
 
792
886
  async _startAsyncJob(args, extra) {
793
- const job = this._createJob(args, 'async');
887
+ const job = this._createJob(args, "async");
794
888
  this.jobs.set(job.jobId, job);
795
889
 
796
- await emitProgress(extra, DIRECT_PROGRESS_START, 100, `비동기 job ${job.jobId}를 시작합니다.`);
890
+ await emitProgress(
891
+ extra,
892
+ DIRECT_PROGRESS_START,
893
+ 100,
894
+ `비동기 job ${job.jobId}를 시작합니다.`,
895
+ );
797
896
 
798
897
  void (async () => {
799
898
  try {
@@ -802,15 +901,20 @@ export class DelegatorMcpWorker {
802
901
  : await this._runAsyncWorker(args, job);
803
902
  this._applyJobResult(job, result);
804
903
  } catch (error) {
805
- this._applyJobResult(job, createErrorPayload(
806
- error instanceof Error ? error.message : String(error),
807
- {
808
- mode: 'async',
809
- providerRequested: args.provider,
810
- agentType: args.agentType,
811
- transport: this._shouldUseRoute(args) ? 'route-script' : `${args.provider}-worker`,
812
- },
813
- ));
904
+ this._applyJobResult(
905
+ job,
906
+ createErrorPayload(
907
+ error instanceof Error ? error.message : String(error),
908
+ {
909
+ mode: "async",
910
+ providerRequested: args.provider,
911
+ agentType: args.agentType,
912
+ transport: this._shouldUseRoute(args)
913
+ ? "route-script"
914
+ : `${args.provider}-worker`,
915
+ },
916
+ ),
917
+ );
814
918
  } finally {
815
919
  if (job.worker) {
816
920
  await job.worker.stop().catch(() => {});
@@ -824,29 +928,32 @@ export class DelegatorMcpWorker {
824
928
  }
825
929
 
826
930
  async _runAsyncWorker(args, job) {
827
- if (args.provider === 'codex') {
828
- const result = await this.codexWorker.execute(this._buildDirectPrompt(args), {
829
- cwd: args.cwd || this.cwd,
830
- timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
831
- sessionKey: args.sessionKey,
832
- threadId: args.threadId,
833
- resetSession: args.resetSession,
834
- profile: resolveCodexProfile(args.agentType),
835
- sandbox: 'danger-full-access',
836
- approvalPolicy: 'never',
837
- developerInstructions: joinInstructions(
838
- REVIEW_INSTRUCTION_BY_AGENT[args.agentType],
839
- this._buildPromptHintInstruction(args),
840
- args.developerInstructions,
841
- ),
842
- config: getCodexMcpConfig(this._getMcpPolicyOptions(args)),
843
- compactPrompt: args.compactPrompt,
844
- model: args.model,
845
- });
931
+ if (args.provider === "codex") {
932
+ const result = await this.codexWorker.execute(
933
+ this._buildDirectPrompt(args),
934
+ {
935
+ cwd: args.cwd || this.cwd,
936
+ timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
937
+ sessionKey: args.sessionKey,
938
+ threadId: args.threadId,
939
+ resetSession: args.resetSession,
940
+ profile: resolveCodexProfile(args.agentType),
941
+ sandbox: "danger-full-access",
942
+ approvalPolicy: "never",
943
+ developerInstructions: joinInstructions(
944
+ REVIEW_INSTRUCTION_BY_AGENT[args.agentType],
945
+ this._buildPromptHintInstruction(args),
946
+ args.developerInstructions,
947
+ ),
948
+ config: getCodexMcpConfig(this._getMcpPolicyOptions(args)),
949
+ compactPrompt: args.compactPrompt,
950
+ model: args.model,
951
+ },
952
+ );
846
953
 
847
954
  return {
848
955
  ok: result.exitCode === 0,
849
- providerResolved: 'codex',
956
+ providerResolved: "codex",
850
957
  output: result.output,
851
958
  exitCode: result.exitCode,
852
959
  threadId: result.threadId,
@@ -854,40 +961,30 @@ export class DelegatorMcpWorker {
854
961
  };
855
962
  }
856
963
 
857
- if (args.provider === 'gemini') {
858
- const worker = this._createGeminiWorker();
859
- job.worker = worker;
860
- const prompt = this._buildDirectPromptWithHint(args);
861
- const result = await worker.execute(prompt, {
862
- cwd: args.cwd || this.cwd,
863
- timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
864
- model: args.model || resolveGeminiModel(args.agentType),
865
- approvalMode: 'yolo',
866
- allowedMcpServerNames: getGeminiAllowedServers(this._getMcpPolicyOptions(args)),
867
- });
868
-
869
- return {
870
- ok: result.exitCode === 0,
871
- providerResolved: 'gemini',
872
- output: result.output,
873
- exitCode: result.exitCode,
874
- sessionKey: result.sessionKey,
875
- _geminiPrompt: prompt,
876
- };
877
- }
878
-
879
964
  throw new Error(`지원하지 않는 async provider: ${args.provider}`);
880
965
  }
881
966
 
882
967
  _buildRouteEnv(args) {
883
- const env = cloneEnv(this.env);
968
+ const baseEnv = cloneEnv(this.env);
969
+ const sandbox = buildWorkerSandboxEnv({
970
+ cwd: args.cwd || this.cwd,
971
+ sessionId:
972
+ args.teamTaskId ||
973
+ args.teamAgentName ||
974
+ args.workerIndex ||
975
+ args.agentType ||
976
+ "route",
977
+ env: baseEnv,
978
+ });
979
+ const env = { ...baseEnv, ...sandbox.env };
884
980
  env.TFX_CLI_MODE = pickRouteMode(args.provider);
981
+ if (sandbox.root) env.TFX_WORKER_SANDBOX_SCOPE = "delegator-route";
885
982
 
886
983
  if (args.codexTransport) {
887
984
  env.TFX_CODEX_TRANSPORT = args.codexTransport;
888
985
  }
889
986
  if (args.noClaudeNative === true) {
890
- env.TFX_NO_CLAUDE_NATIVE = '1';
987
+ env.TFX_NO_CLAUDE_NATIVE = "1";
891
988
  }
892
989
  if (args.searchTool) {
893
990
  env.TFX_SEARCH_TOOL = args.searchTool;
@@ -905,19 +1002,19 @@ export class DelegatorMcpWorker {
905
1002
  }
906
1003
 
907
1004
  async _spawnRoute(args, job = null) {
908
- const prompt = withContext(String(args.prompt ?? ''), args.contextFile);
1005
+ const prompt = withContext(String(args.prompt ?? ""), args.contextFile);
909
1006
  const childArgs = [
910
1007
  this.routeScript,
911
- args.agentType || 'executor',
1008
+ args.agentType || "executor",
912
1009
  prompt,
913
- args.mcpProfile || 'auto',
1010
+ args.mcpProfile || "auto",
914
1011
  String(resolveTimeoutSec(args.agentType, args.timeoutMs)),
915
1012
  ];
916
1013
 
917
1014
  const child = spawn(this.bashCommand, childArgs, {
918
1015
  cwd: args.cwd || this.cwd,
919
1016
  env: this._buildRouteEnv(args),
920
- stdio: ['ignore', 'pipe', 'pipe'],
1017
+ stdio: ["ignore", "pipe", "pipe"],
921
1018
  windowsHide: true,
922
1019
  });
923
1020
 
@@ -931,16 +1028,16 @@ export class DelegatorMcpWorker {
931
1028
  const stdoutChunks = [];
932
1029
  const stderrChunks = [];
933
1030
 
934
- child.stdout.on('data', (chunk) => stdoutChunks.push(Buffer.from(chunk)));
935
- child.stderr.on('data', (chunk) => stderrChunks.push(Buffer.from(chunk)));
936
- child.once('error', (error) => {
1031
+ child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.from(chunk)));
1032
+ child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.from(chunk)));
1033
+ child.once("error", (error) => {
937
1034
  this.routeChildren.delete(child);
938
1035
  rejectPromise(error);
939
1036
  });
940
- child.once('close', (code) => {
1037
+ child.once("close", (code) => {
941
1038
  this.routeChildren.delete(child);
942
- const stdout = Buffer.concat(stdoutChunks).toString('utf8');
943
- const stderr = Buffer.concat(stderrChunks).toString('utf8');
1039
+ const stdout = Buffer.concat(stdoutChunks).toString("utf8");
1040
+ const stderr = Buffer.concat(stderrChunks).toString("utf8");
944
1041
  resolvePromise({
945
1042
  ok: code === 0,
946
1043
  providerResolved: parseRouteType(stderr) || args.provider,
@@ -957,7 +1054,7 @@ export class DelegatorMcpWorker {
957
1054
  return {
958
1055
  ok: job.ok,
959
1056
  job_id: job.jobId,
960
- mode: job.mode || 'async',
1057
+ mode: job.mode || "async",
961
1058
  status: job.status,
962
1059
  provider_requested: job.providerRequested,
963
1060
  provider_resolved: job.providerResolved,
@@ -969,10 +1066,10 @@ export class DelegatorMcpWorker {
969
1066
  completed_at: job.completedAt,
970
1067
  output: job.output,
971
1068
  stderr: job.stderr,
972
- error: '',
1069
+ error: "",
973
1070
  thread_id: job.threadId,
974
1071
  session_key: job.sessionKey,
975
- conversation_open: this.geminiConversations.has(job.jobId),
1072
+ conversation_open: false,
976
1073
  };
977
1074
  }
978
1075
 
@@ -983,17 +1080,19 @@ export class DelegatorMcpWorker {
983
1080
  ok: true,
984
1081
  jobId,
985
1082
  mode,
986
- status: 'running',
1083
+ status: "running",
987
1084
  providerRequested: args.provider,
988
1085
  providerResolved: null,
989
1086
  agentType: args.agentType,
990
- transport: this._shouldUseRoute(args) ? 'route-script' : `${args.provider}-worker`,
1087
+ transport: this._shouldUseRoute(args)
1088
+ ? "route-script"
1089
+ : `${args.provider}-worker`,
991
1090
  createdAt: now,
992
1091
  startedAt: now,
993
1092
  updatedAt: now,
994
1093
  completedAt: null,
995
- output: '',
996
- stderr: '',
1094
+ output: "",
1095
+ stderr: "",
997
1096
  exitCode: null,
998
1097
  threadId: null,
999
1098
  sessionKey: args.sessionKey || null,
@@ -1005,50 +1104,20 @@ export class DelegatorMcpWorker {
1005
1104
 
1006
1105
  _applyJobResult(job, result = {}) {
1007
1106
  job.ok = result.ok !== false;
1008
- job.status = job.ok ? 'completed' : 'failed';
1107
+ job.status = job.ok ? "completed" : "failed";
1009
1108
  job.providerResolved = result.providerResolved || job.providerRequested;
1010
1109
  job.transport = result.transport || job.transport;
1011
- job.output = result.output || '';
1012
- job.stderr = result.stderr || result.error || '';
1110
+ job.output = result.output || "";
1111
+ job.stderr = result.stderr || result.error || "";
1013
1112
  job.exitCode = result.exitCode ?? (job.ok ? 0 : 1);
1014
1113
  job.threadId = result.threadId || job.threadId || null;
1015
1114
  job.sessionKey = result.sessionKey || job.sessionKey || null;
1016
1115
  job.completedAt = new Date().toISOString();
1017
1116
  job.updatedAt = job.completedAt;
1018
-
1019
- if (job.providerRequested === 'gemini'
1020
- && job.transport === 'gemini-worker'
1021
- && typeof result._geminiPrompt === 'string') {
1022
- this._storeGeminiConversation(job, result._geminiPrompt, result.output || '');
1023
- }
1024
- }
1025
-
1026
- _storeGeminiConversation(job, userPrompt, assistantReply) {
1027
- const existing = this.geminiConversations.get(job.jobId);
1028
- if (existing) {
1029
- if (typeof assistantReply === 'string') {
1030
- const lastTurn = existing.turns.at(-1);
1031
- if (lastTurn && lastTurn.assistant !== assistantReply) {
1032
- lastTurn.assistant = assistantReply;
1033
- }
1034
- }
1035
- existing.updatedAt = new Date().toISOString();
1036
- return;
1037
- }
1038
-
1039
- this.geminiConversations.set(job.jobId, {
1040
- jobId: job.jobId,
1041
- closed: false,
1042
- updatedAt: new Date().toISOString(),
1043
- turns: [{
1044
- user: userPrompt,
1045
- assistant: assistantReply,
1046
- }],
1047
- });
1048
1117
  }
1049
1118
 
1050
1119
  async _runSyncJob(args, extra) {
1051
- const job = this._createJob(args, 'sync');
1120
+ const job = this._createJob(args, "sync");
1052
1121
  this.jobs.set(job.jobId, job);
1053
1122
  const result = await this._executeDirect(args, extra);
1054
1123
  this._applyJobResult(job, result);
@@ -1065,7 +1134,9 @@ export async function runDelegatorMcpCli() {
1065
1134
  try {
1066
1135
  await worker.serveStdio();
1067
1136
  } catch (error) {
1068
- console.error(`[delegator-mcp] ${error instanceof Error ? error.message : String(error)}`);
1137
+ console.error(
1138
+ `[delegator-mcp] ${error instanceof Error ? error.message : String(error)}`,
1139
+ );
1069
1140
  process.exitCode = 1;
1070
1141
  }
1071
1142
  }