@triflux/remote 10.50.1 → 10.51.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 (120) hide show
  1. package/cto/brief.mjs +0 -12
  2. package/cto/collect.mjs +16 -247
  3. package/cto/current.schema.json +4 -20
  4. package/cto/events.mjs +13 -294
  5. package/cto/hygiene.mjs +26 -346
  6. package/cto/lake-root.mjs +114 -16
  7. package/cto/status.mjs +33 -128
  8. package/hub/index.mjs +0 -8
  9. package/hub/store-adapter.mjs +1 -1
  10. package/hub/team/agent-map.json +0 -1
  11. package/hub/team/ansi.mjs +1 -35
  12. package/hub/team/backend.mjs +9 -145
  13. package/hub/team/cli/commands/attach.mjs +1 -17
  14. package/hub/team/cli/commands/control.mjs +12 -38
  15. package/hub/team/cli/commands/debug.mjs +1 -37
  16. package/hub/team/cli/commands/focus.mjs +5 -31
  17. package/hub/team/cli/commands/interrupt.mjs +6 -25
  18. package/hub/team/cli/commands/kill.mjs +1 -30
  19. package/hub/team/cli/commands/list.mjs +0 -16
  20. package/hub/team/cli/commands/send.mjs +6 -26
  21. package/hub/team/cli/commands/start/index.mjs +41 -133
  22. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  23. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  24. package/hub/team/cli/commands/start/start-mux.mjs +0 -3
  25. package/hub/team/cli/commands/status.mjs +2 -57
  26. package/hub/team/cli/commands/stop.mjs +14 -19
  27. package/hub/team/cli/help.mjs +4 -5
  28. package/hub/team/cli/manifest.mjs +5 -1
  29. package/hub/team/cli/render.mjs +0 -12
  30. package/hub/team/cli/services/hub-client.mjs +2 -88
  31. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  32. package/hub/team/handoff.mjs +3 -34
  33. package/hub/team/headless.mjs +14 -755
  34. package/hub/team/orchestrator.mjs +4 -30
  35. package/hub/team/pane.mjs +6 -28
  36. package/hub/team/psmux.mjs +16 -403
  37. package/hub/team/remote-session.mjs +1 -2
  38. package/hub/team/session.mjs +8 -119
  39. package/hub/team/swarm-intent.mjs +0 -198
  40. package/hub/team/terminal-opener.mjs +11 -35
  41. package/hub/team/tui-core.mjs +1 -1
  42. package/hub/team/tui-viewer.mjs +2 -2
  43. package/hub/team/tui.mjs +8 -269
  44. package/hub/team/wt-manager.mjs +2 -2
  45. package/hub/workers/factory.mjs +4 -37
  46. package/hub/workers/lib/jsonrpc-ws-uds.mjs +0 -2
  47. package/package.json +1 -1
  48. package/scripts/lib/agent-route-policy.mjs +162 -14
  49. package/scripts/lib/claudemd-scanner.mjs +2 -199
  50. package/scripts/lib/cli-claude.mjs +0 -2
  51. package/scripts/lib/context.mjs +3 -19
  52. package/scripts/lib/env-probe.mjs +5 -64
  53. package/scripts/lib/gemini-profiles.mjs +2 -69
  54. package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
  55. package/scripts/lib/machine-profile.mjs +3 -17
  56. package/scripts/lib/mcp-filter.mjs +109 -53
  57. package/scripts/lib/mcp-health.mjs +2 -2
  58. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  59. package/scripts/lib/psmux-info.mjs +25 -104
  60. package/scripts/lib/skill-template.mjs +0 -271
  61. package/cto/hygiene-actions.mjs +0 -355
  62. package/cto/hygiene-notify.mjs +0 -170
  63. package/hub/team/build-worker-prompt.mjs +0 -92
  64. package/hub/team/check-mcp-hub.mjs +0 -49
  65. package/hub/team/claude-native-bridge.mjs +0 -987
  66. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  67. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  68. package/hub/team/cli/services/native-control.mjs +0 -145
  69. package/hub/team/codex-review.mjs +0 -520
  70. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  71. package/hub/team/conductor.mjs +0 -1529
  72. package/hub/team/consensus-meta.mjs +0 -176
  73. package/hub/team/cto-pull.mjs +0 -31
  74. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  75. package/hub/team/dashboard.mjs +0 -322
  76. package/hub/team/event-log.mjs +0 -84
  77. package/hub/team/execution-mode.mjs +0 -324
  78. package/hub/team/extract-completion-payload.mjs +0 -110
  79. package/hub/team/headless-bridge-session.mjs +0 -17
  80. package/hub/team/health-probe.mjs +0 -435
  81. package/hub/team/interactive-native-launcher.mjs +0 -119
  82. package/hub/team/interactive-native-worker.mjs +0 -80
  83. package/hub/team/interactive-tui-transport.mjs +0 -479
  84. package/hub/team/launcher-template.mjs +0 -144
  85. package/hub/team/mcp-selector.mjs +0 -145
  86. package/hub/team/native-supervisor.mjs +0 -460
  87. package/hub/team/native.mjs +0 -727
  88. package/hub/team/recovery-store.mjs +0 -149
  89. package/hub/team/remote-probe.mjs +0 -292
  90. package/hub/team/remote-watcher.mjs +0 -506
  91. package/hub/team/runtime-strategy.mjs +0 -200
  92. package/hub/team/schemas/worker-completion.json +0 -39
  93. package/hub/team/sentinel-capture.mjs +0 -127
  94. package/hub/team/swarm-cli.mjs +0 -390
  95. package/hub/team/swarm-hypervisor.mjs +0 -2206
  96. package/hub/team/swarm-locks.mjs +0 -335
  97. package/hub/team/swarm-planner.mjs +0 -371
  98. package/hub/team/swarm-preflight.mjs +0 -366
  99. package/hub/team/swarm-reconciler.mjs +0 -242
  100. package/hub/team/tui-remote-adapter.mjs +0 -377
  101. package/hub/team/worker-completion-validator.mjs +0 -79
  102. package/hub/team/worktree-lifecycle.mjs +0 -639
  103. package/hub/team/wt-templates.json +0 -43
  104. package/hub/workers/antigravity-route-worker.mjs +0 -179
  105. package/hub/workers/gemini-worker.mjs +0 -217
  106. package/scripts/lib/agent-json.mjs +0 -27
  107. package/scripts/lib/async.mjs +0 -174
  108. package/scripts/lib/cli-agy.mjs +0 -85
  109. package/scripts/lib/cli-codex.mjs +0 -62
  110. package/scripts/lib/cli-gemini.mjs +0 -37
  111. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  112. package/scripts/lib/env.mjs +0 -14
  113. package/scripts/lib/hub.mjs +0 -48
  114. package/scripts/lib/pid.mjs +0 -63
  115. package/scripts/lib/quota.mjs +0 -47
  116. package/scripts/lib/skill-state.mjs +0 -220
  117. package/scripts/lib/team.mjs +0 -75
  118. package/scripts/lib/timeout.mjs +0 -66
  119. package/scripts/lib/tmp.mjs +0 -26
  120. package/scripts/lib/toml.mjs +0 -132
@@ -1,479 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { mkdtemp, open, rm } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import path from "node:path";
5
- import { StringDecoder } from "node:string_decoder";
6
- import { setTimeout as sleep } from "node:timers/promises";
7
- import { promisify } from "node:util";
8
-
9
- const execFileAsync = promisify(execFile);
10
- const MAX_BUFFER = 5 * 1024 * 1024;
11
- const DEFAULT_POLL_INTERVAL_MS = 150;
12
- const MAX_CONSECUTIVE_POLL_ERRORS = 3;
13
- const STARTUP_PROMPT_MAX_ATTEMPTS = 30;
14
- const STARTUP_PROMPT_POLL_INTERVAL_MS = 300;
15
- const PIPE_TEMP_PREFIX = "triflux-tui-pipe-";
16
- const PIPE_OUTPUT_FILE = "pane.out";
17
- const CODEX_NO_APPROVAL_FLAG = "--dangerously-bypass-approvals-and-sandbox";
18
-
19
- const CONTROL_SEQUENCES = [
20
- ["\x1b[A", "Up"],
21
- ["\x1b[B", "Down"],
22
- ["\x1b[C", "Right"],
23
- ["\x1b[D", "Left"],
24
- ["\x1b[3~", "DC"],
25
- ["\x1b[H", "Home"],
26
- ["\x1b[F", "End"],
27
- ];
28
-
29
- const CONTROL_CHARS = new Map([
30
- ["\r", "Enter"],
31
- ["\n", "Enter"],
32
- ["\t", "Tab"],
33
- ["\x7f", "BSpace"],
34
- ["\b", "BSpace"],
35
- ["\x1b", "Escape"],
36
- ]);
37
-
38
- export async function defaultRunTmux(args, options = {}) {
39
- return execFileAsync("tmux", args, {
40
- timeout: options.timeout ?? 15_000,
41
- maxBuffer: MAX_BUFFER,
42
- });
43
- }
44
-
45
- export function ensureInteractiveCodexPosture(launchCmd) {
46
- const command = String(launchCmd || "").trim();
47
- if (!/^codex(?:\s|$)/u.test(command)) return command;
48
- if (command.includes(CODEX_NO_APPROVAL_FLAG)) return command;
49
- return command.replace(/^codex/u, `codex ${CODEX_NO_APPROVAL_FLAG}`);
50
- }
51
-
52
- export function createInteractiveTuiTransport({
53
- runTmux = defaultRunTmux,
54
- sessionName,
55
- cwd = null,
56
- launchCmd = "codex",
57
- env = {},
58
- onData = () => {},
59
- pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
60
- } = {}) {
61
- if (!sessionName || typeof sessionName !== "string") {
62
- throw new Error("sessionName is required");
63
- }
64
- if (!Number.isInteger(pollIntervalMs) || pollIntervalMs <= 0) {
65
- throw new Error("pollIntervalMs must be a positive integer");
66
- }
67
-
68
- let started = false;
69
- let stopped = false;
70
- let capturePollTimer = null;
71
- let capturePollInFlight = false;
72
- let previousCapture = "";
73
- let consecutivePollErrors = 0;
74
- let pipeTimer = null;
75
- let pipeReadInFlight = false;
76
- let pipeTempDir = null;
77
- let pipeOutputPath = null;
78
- let pipeReadOffset = 0;
79
- let pipeDecoder = null;
80
- let pipeEnabled = false;
81
- let consecutivePipeReadErrors = 0;
82
-
83
- async function tmux(args, options) {
84
- return runTmux(args, options);
85
- }
86
-
87
- async function captureVisible() {
88
- const { stdout = "" } = await tmux([
89
- "capture-pane",
90
- "-t",
91
- sessionName,
92
- "-p",
93
- ]);
94
- return stdout;
95
- }
96
-
97
- async function capturePane() {
98
- const { stdout = "" } = await tmux([
99
- "capture-pane",
100
- "-e",
101
- "-t",
102
- sessionName,
103
- "-p",
104
- ]);
105
- return stdout;
106
- }
107
-
108
- async function start() {
109
- if (started) {
110
- return;
111
- }
112
- if (stopped) {
113
- throw new Error("transport has been stopped");
114
- }
115
-
116
- await tmux([
117
- "new-session",
118
- "-d",
119
- "-s",
120
- sessionName,
121
- ...(cwd ? ["-c", cwd] : []),
122
- ...buildTmuxEnvArgs(env),
123
- ]);
124
- await tmux([
125
- "send-keys",
126
- "-t",
127
- sessionName,
128
- ensureInteractiveCodexPosture(launchCmd),
129
- "Enter",
130
- ]);
131
- await dismissStartupPrompts({ tmux, captureVisible, sessionName });
132
- started = true;
133
- await startOutputStream();
134
- }
135
-
136
- async function writeInput(bytesOrText) {
137
- const events = inputToTmuxEvents(bytesOrText);
138
- for (const event of events) {
139
- if (event.type === "text") {
140
- await tmux(["send-keys", "-t", sessionName, "-l", "--", event.text]);
141
- } else {
142
- await tmux(["send-keys", "-t", sessionName, event.key]);
143
- }
144
- }
145
- }
146
-
147
- async function resize({ cols, rows } = {}) {
148
- assertPositiveInteger("cols", cols);
149
- assertPositiveInteger("rows", rows);
150
- await tmux([
151
- "resize-window",
152
- "-t",
153
- sessionName,
154
- "-x",
155
- String(cols),
156
- "-y",
157
- String(rows),
158
- ]);
159
- }
160
-
161
- async function stop() {
162
- if (stopped) {
163
- return;
164
- }
165
- stopped = true;
166
- await stopOutputStream();
167
- await tmux(["kill-session", "-t", sessionName]);
168
- }
169
-
170
- async function startOutputStream() {
171
- try {
172
- await startPipeStream();
173
- } catch {
174
- await stopPipeStream({ disableTmux: false });
175
- startCapturePolling();
176
- }
177
- }
178
-
179
- async function stopOutputStream() {
180
- stopCapturePolling();
181
- await stopPipeStream();
182
- }
183
-
184
- async function startPipeStream() {
185
- pipeTempDir = await mkdtemp(path.join(tmpdir(), PIPE_TEMP_PREFIX));
186
- pipeOutputPath = path.join(pipeTempDir, PIPE_OUTPUT_FILE);
187
- pipeReadOffset = 0;
188
- pipeDecoder = new StringDecoder("utf8");
189
-
190
- await tmux([
191
- "pipe-pane",
192
- "-t",
193
- sessionName,
194
- "-o",
195
- `cat >> ${shellQuote(pipeOutputPath)}`,
196
- ]);
197
- pipeEnabled = true;
198
- startPipeReader();
199
- }
200
-
201
- function startPipeReader() {
202
- pipeTimer = setInterval(() => {
203
- void readPipeOutput();
204
- }, pollIntervalMs);
205
- void readPipeOutput();
206
- }
207
-
208
- async function readPipeOutput() {
209
- if (stopped || !pipeOutputPath || pipeReadInFlight) {
210
- return;
211
- }
212
- pipeReadInFlight = true;
213
- let handle = null;
214
- try {
215
- handle = await open(pipeOutputPath, "r");
216
- const stats = await handle.stat();
217
- consecutivePipeReadErrors = 0;
218
- if (stats.size < pipeReadOffset) {
219
- pipeReadOffset = 0;
220
- }
221
- const bytesToRead = stats.size - pipeReadOffset;
222
- if (bytesToRead > 0) {
223
- const buffer = Buffer.allocUnsafe(bytesToRead);
224
- const { bytesRead } = await handle.read(
225
- buffer,
226
- 0,
227
- bytesToRead,
228
- pipeReadOffset,
229
- );
230
- if (bytesRead > 0 && pipeDecoder) {
231
- pipeReadOffset += bytesRead;
232
- const chunk = pipeDecoder.write(buffer.subarray(0, bytesRead));
233
- if (chunk) {
234
- onData(chunk);
235
- }
236
- }
237
- }
238
- } catch {
239
- consecutivePipeReadErrors += 1;
240
- if (
241
- !stopped &&
242
- pipeEnabled &&
243
- consecutivePipeReadErrors >= MAX_CONSECUTIVE_POLL_ERRORS
244
- ) {
245
- await stopPipeStream();
246
- startCapturePolling();
247
- }
248
- } finally {
249
- if (handle) {
250
- await handle.close().catch(() => {});
251
- }
252
- pipeReadInFlight = false;
253
- }
254
- }
255
-
256
- async function stopPipeStream({ disableTmux = true } = {}) {
257
- stopPipeReader();
258
- if (pipeEnabled && disableTmux) {
259
- await tmux(["pipe-pane", "-t", sessionName]).catch(() => {});
260
- }
261
- pipeEnabled = false;
262
- pipeDecoder = null;
263
- pipeReadOffset = 0;
264
- consecutivePipeReadErrors = 0;
265
- pipeOutputPath = null;
266
- const tempDir = pipeTempDir;
267
- pipeTempDir = null;
268
- if (tempDir) {
269
- await rm(tempDir, { recursive: true, force: true });
270
- }
271
- }
272
-
273
- function stopPipeReader() {
274
- if (pipeTimer) {
275
- clearInterval(pipeTimer);
276
- pipeTimer = null;
277
- }
278
- }
279
-
280
- function startCapturePolling() {
281
- capturePollTimer = setInterval(() => {
282
- void pollCaptureOutput();
283
- }, pollIntervalMs);
284
- void pollCaptureOutput();
285
- }
286
-
287
- async function pollCaptureOutput() {
288
- if (stopped || capturePollInFlight) {
289
- return;
290
- }
291
- capturePollInFlight = true;
292
- try {
293
- const raw = await capturePane();
294
- consecutivePollErrors = 0;
295
- if (raw && raw !== previousCapture) {
296
- previousCapture = raw;
297
- onData(raw);
298
- } else if (!raw) {
299
- previousCapture = raw;
300
- }
301
- } catch {
302
- consecutivePollErrors += 1;
303
- if (consecutivePollErrors >= MAX_CONSECUTIVE_POLL_ERRORS) {
304
- stopCapturePolling();
305
- }
306
- } finally {
307
- capturePollInFlight = false;
308
- }
309
- }
310
-
311
- function stopCapturePolling() {
312
- if (capturePollTimer) {
313
- clearInterval(capturePollTimer);
314
- capturePollTimer = null;
315
- }
316
- }
317
-
318
- return {
319
- start,
320
- writeInput,
321
- resize,
322
- stop,
323
- };
324
- }
325
-
326
- function shellQuote(value) {
327
- return `'${String(value).replaceAll("'", "'\\''")}'`;
328
- }
329
-
330
- function buildTmuxEnvArgs(env) {
331
- return Object.entries(env)
332
- .filter(([, value]) => value !== undefined && value !== null)
333
- .sort(([left], [right]) => left.localeCompare(right))
334
- .flatMap(([key, value]) => {
335
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
336
- throw new Error(`invalid tmux env key: ${key}`);
337
- }
338
- return ["-e", `${key}=${String(value)}`];
339
- });
340
- }
341
-
342
- function inputToTmuxEvents(bytesOrText) {
343
- const text =
344
- bytesOrText instanceof Uint8Array
345
- ? Buffer.from(bytesOrText).toString("utf8")
346
- : String(bytesOrText ?? "");
347
- const events = [];
348
- let literal = "";
349
-
350
- function flushLiteral() {
351
- if (literal) {
352
- events.push({ type: "text", text: literal });
353
- literal = "";
354
- }
355
- }
356
-
357
- for (let index = 0; index < text.length; ) {
358
- const sequence = CONTROL_SEQUENCES.find(([candidate]) =>
359
- text.startsWith(candidate, index),
360
- );
361
- if (sequence) {
362
- flushLiteral();
363
- events.push({ type: "key", key: sequence[1] });
364
- index += sequence[0].length;
365
- continue;
366
- }
367
-
368
- const char = text[index];
369
- const mapped = mapControlChar(char);
370
- if (mapped) {
371
- flushLiteral();
372
- events.push({ type: "key", key: mapped });
373
- index += 1;
374
- continue;
375
- }
376
-
377
- literal += char;
378
- index += 1;
379
- }
380
-
381
- flushLiteral();
382
- return events;
383
- }
384
-
385
- function mapControlChar(char) {
386
- const direct = CONTROL_CHARS.get(char);
387
- if (direct) {
388
- return direct;
389
- }
390
-
391
- const code = char.charCodeAt(0);
392
- if (code >= 1 && code <= 26) {
393
- return `C-${String.fromCharCode(96 + code)}`;
394
- }
395
- return null;
396
- }
397
-
398
- function assertPositiveInteger(name, value) {
399
- if (!Number.isInteger(value) || value <= 0) {
400
- throw new Error(`${name} must be a positive integer`);
401
- }
402
- }
403
-
404
- function isUpdatePrompt(text) {
405
- return (
406
- /Update available/.test(text) &&
407
- /Update now/.test(text) &&
408
- /\bSkip\b/.test(text)
409
- );
410
- }
411
-
412
- function isCodexTrustPrompt(text) {
413
- return String(text).includes("Do you trust the contents of this directory");
414
- }
415
-
416
- function isCodexMainPrompt(text) {
417
- const raw = String(text);
418
- return /\/model to change/.test(raw) || /^\s*›\s*$/m.test(raw);
419
- }
420
-
421
- function selectedLine(text) {
422
- return (
423
- String(text)
424
- .split("\n")
425
- .find((line) => /^\s*[›❯▶▸]\s*/.test(line)) ?? ""
426
- );
427
- }
428
-
429
- function isUpdateNowLine(line) {
430
- return /Update now/.test(line);
431
- }
432
-
433
- function isSafeSkipLine(line) {
434
- return /\bSkip\b/.test(line) && !isUpdateNowLine(line);
435
- }
436
-
437
- async function dismissStartupPrompts({ tmux, captureVisible, sessionName }) {
438
- for (let attempt = 0; attempt < STARTUP_PROMPT_MAX_ATTEMPTS; attempt += 1) {
439
- const raw = await captureVisible();
440
-
441
- if (isUpdatePrompt(raw)) {
442
- await dismissCodexUpdatePromptStep({ tmux, raw, sessionName });
443
- await sleep(STARTUP_PROMPT_POLL_INTERVAL_MS);
444
- continue;
445
- }
446
-
447
- if (isCodexTrustPrompt(raw)) {
448
- await dismissCodexTrustPromptStep({ tmux, raw, sessionName });
449
- await sleep(STARTUP_PROMPT_POLL_INTERVAL_MS);
450
- continue;
451
- }
452
-
453
- if (isCodexMainPrompt(raw)) {
454
- return;
455
- }
456
-
457
- await sleep(STARTUP_PROMPT_POLL_INTERVAL_MS);
458
- }
459
- }
460
-
461
- async function dismissCodexUpdatePromptStep({ tmux, raw, sessionName }) {
462
- const selected = selectedLine(raw);
463
- if (isSafeSkipLine(selected)) {
464
- await tmux(["send-keys", "-t", sessionName, "Enter"]);
465
- return;
466
- }
467
-
468
- await tmux(["send-keys", "-t", sessionName, "Down"]);
469
- }
470
-
471
- async function dismissCodexTrustPromptStep({ tmux, raw, sessionName }) {
472
- const selected = selectedLine(raw);
473
- if (/Yes, continue/.test(selected) && !/No, quit/.test(selected)) {
474
- await tmux(["send-keys", "-t", sessionName, "Enter"]);
475
- return;
476
- }
477
-
478
- await tmux(["send-keys", "-t", sessionName, "Down"]);
479
- }
@@ -1,144 +0,0 @@
1
- // hub/team/launcher-template.mjs — 결정론적 런처 생성
2
- // 기존 codex-adapter와 Antigravity print-value 계약을 소비하여
3
- // 동일 입력 → 동일 args 배열을 보장한다.
4
- // F1 해결: codex adapter가 --dangerously-bypass-approvals-and-sandbox 자동 추가
5
- // F4 해결: codex exec "prompt" 인라인 (파이프/리다이렉트 아님)
6
- // F5 해결: 동일 입력 → 동일 args 배열 (런타임 분기 없음)
7
-
8
- import {
9
- buildDisabledCliError,
10
- normalizeCliName,
11
- resolveCliPolicy,
12
- } from "../../scripts/lib/machine-profile.mjs";
13
- import { buildExecArgs as buildCodexArgs } from "@triflux/core/hub/codex-adapter.mjs";
14
-
15
- function shellSingleQuote(value) {
16
- return `'${String(value).replace(/'/g, "'\\''")}'`;
17
- }
18
-
19
- function buildAntigravityArgs(opts = {}) {
20
- const prompt = typeof opts.prompt === "string" ? opts.prompt : "";
21
- // agy 1.1.27: --print 는 값(프롬프트) 필수, stdin 프롬프트 불가.
22
- const command = `agy --dangerously-skip-permissions --print ${shellSingleQuote(prompt)}`;
23
- if (opts.resultFile) {
24
- return `${command} > ${shellSingleQuote(opts.resultFile)} 2>${shellSingleQuote(`${opts.resultFile}.err`)}`;
25
- }
26
- return command;
27
- }
28
-
29
- /** CLI별 adapter 레지스트리 */
30
- const ADAPTERS = Object.freeze({
31
- codex: {
32
- bin: "codex",
33
- buildArgs: buildCodexArgs,
34
- env: (profile) => (profile ? { CODEX_PROFILE: profile } : {}),
35
- },
36
- gemini: {
37
- bin: "agy",
38
- buildArgs: buildAntigravityArgs,
39
- env: () => ({}),
40
- },
41
- antigravity: {
42
- bin: "agy",
43
- buildArgs: buildAntigravityArgs,
44
- env: () => ({}),
45
- },
46
- agy: {
47
- bin: "agy",
48
- buildArgs: buildAntigravityArgs,
49
- env: () => ({}),
50
- },
51
- claude: {
52
- bin: "claude",
53
- buildArgs: (opts = {}) => {
54
- const parts = ["claude"];
55
- if (opts.model) parts.push("--model", opts.model);
56
- parts.push("-p", JSON.stringify(opts.prompt || ""));
57
- return parts.join(" ");
58
- },
59
- env: () => ({}),
60
- },
61
- });
62
-
63
- /**
64
- * CLI adapter 조회.
65
- * @param {'codex'|'gemini'|'antigravity'|'agy'|'claude'} agent
66
- * @returns {object} adapter — { bin, buildArgs, env }
67
- * @throws {Error} 알 수 없는 agent
68
- */
69
- export function getAdapter(agent) {
70
- const adapter = ADAPTERS[agent];
71
- if (!adapter) {
72
- throw new Error(
73
- `Unknown agent: "${agent}". Supported: ${Object.keys(ADAPTERS).join(", ")}`,
74
- );
75
- }
76
- return adapter;
77
- }
78
-
79
- /**
80
- * 결정론적 런처 생성.
81
- * 동일 입력이면 항상 동일한 { bin, command, env } 반환.
82
- *
83
- * @param {object} opts
84
- * @param {'codex'|'gemini'|'antigravity'|'agy'|'claude'} opts.agent — CLI 타입
85
- * @param {string} [opts.profile] — CLI 프로파일
86
- * @param {string} opts.prompt — 실행할 프롬프트
87
- * @param {string} [opts.workdir] — 작업 디렉토리
88
- * @param {string} [opts.model] — 모델 오버라이드
89
- * @param {string} [opts.resultFile] — 결과 저장 경로
90
- * @returns {{ bin: string, command: string, env: object, agent: string }}
91
- */
92
- export function buildLauncher(opts) {
93
- const { agent, profile, prompt, workdir, model, resultFile, mcpServers } =
94
- opts;
95
-
96
- if (!agent) throw new Error("agent is required");
97
- if (!prompt && prompt !== "") throw new Error("prompt is required");
98
-
99
- // TFX_DISABLE_* SSOT 집행. 이 런처는 tfx-route.sh 를 거치지 않고 codex/agy
100
- // 바이너리를 직접 실행하는 경로라서 같은 정책을 여기서 다시 확인한다. 정책
101
- // 판독 시임은 opts.cliPolicy(계산된 정책)와 opts.policyEnv(env 객체)뿐이다.
102
- // claude 처럼 정책 대상이 아닌 CLI 는 프로파일 파일을 읽지 않는다.
103
- if (normalizeCliName(agent)) {
104
- const cliPolicy =
105
- opts.cliPolicy ?? resolveCliPolicy(opts.policyEnv ?? process.env);
106
- const blockedCli = buildDisabledCliError(agent, cliPolicy);
107
- if (blockedCli) throw blockedCli;
108
- }
109
-
110
- const adapter = getAdapter(agent);
111
-
112
- const command = adapter.buildArgs({
113
- prompt,
114
- profile,
115
- model,
116
- resultFile,
117
- workdir,
118
- cwd: workdir,
119
- mcpServers,
120
- // launcher 는 동일 입력 → 동일 명령 (F5 결정론 보장). buildExecCommand 의
121
- // stdin redirect 모드는 timestamp/pid/counter 가 들어가 비결정적이라
122
- // launcher path 에서는 argv-inline 모드로 강제. headless 워커 spawn 은
123
- // backend.mjs → buildExecArgs (stdinPrompt 미명시) 로 default stdin on.
124
- stdinPrompt: false,
125
- });
126
-
127
- const env = adapter.env(profile);
128
-
129
- return Object.freeze({
130
- bin: adapter.bin,
131
- command,
132
- env,
133
- agent,
134
- cwd: workdir || null,
135
- });
136
- }
137
-
138
- /**
139
- * 지원되는 agent 목록.
140
- * @returns {string[]}
141
- */
142
- export function listAgents() {
143
- return Object.keys(ADAPTERS);
144
- }