@triflux/remote 10.50.2 → 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 (97) hide show
  1. package/cto/brief.mjs +0 -12
  2. package/cto/collect.mjs +0 -73
  3. package/cto/current.schema.json +0 -4
  4. package/cto/status.mjs +2 -50
  5. package/hub/index.mjs +0 -8
  6. package/hub/team/agent-map.json +0 -1
  7. package/hub/team/ansi.mjs +1 -35
  8. package/hub/team/backend.mjs +9 -145
  9. package/hub/team/cli/commands/attach.mjs +1 -17
  10. package/hub/team/cli/commands/control.mjs +12 -38
  11. package/hub/team/cli/commands/debug.mjs +1 -37
  12. package/hub/team/cli/commands/focus.mjs +5 -31
  13. package/hub/team/cli/commands/interrupt.mjs +6 -25
  14. package/hub/team/cli/commands/kill.mjs +1 -30
  15. package/hub/team/cli/commands/list.mjs +0 -16
  16. package/hub/team/cli/commands/send.mjs +6 -26
  17. package/hub/team/cli/commands/start/index.mjs +41 -133
  18. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  19. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  20. package/hub/team/cli/commands/start/start-mux.mjs +0 -3
  21. package/hub/team/cli/commands/status.mjs +2 -57
  22. package/hub/team/cli/commands/stop.mjs +14 -19
  23. package/hub/team/cli/help.mjs +4 -5
  24. package/hub/team/cli/manifest.mjs +5 -1
  25. package/hub/team/cli/render.mjs +0 -12
  26. package/hub/team/cli/services/hub-client.mjs +2 -88
  27. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  28. package/hub/team/handoff.mjs +3 -34
  29. package/hub/team/headless.mjs +13 -754
  30. package/hub/team/orchestrator.mjs +4 -30
  31. package/hub/team/pane.mjs +6 -28
  32. package/hub/team/psmux.mjs +0 -1
  33. package/hub/team/remote-session.mjs +1 -2
  34. package/hub/team/session.mjs +7 -91
  35. package/hub/team/swarm-intent.mjs +0 -198
  36. package/hub/team/tui-core.mjs +1 -1
  37. package/hub/team/tui-viewer.mjs +2 -2
  38. package/hub/team/tui.mjs +8 -269
  39. package/hub/team/wt-manager.mjs +2 -2
  40. package/hub/workers/factory.mjs +4 -37
  41. package/hub/workers/lib/jsonrpc-ws-uds.mjs +0 -2
  42. package/package.json +1 -1
  43. package/scripts/lib/agent-route-policy.mjs +162 -14
  44. package/scripts/lib/claudemd-scanner.mjs +2 -199
  45. package/scripts/lib/cli-claude.mjs +0 -2
  46. package/scripts/lib/context.mjs +3 -19
  47. package/scripts/lib/env-probe.mjs +5 -64
  48. package/scripts/lib/gemini-profiles.mjs +2 -69
  49. package/scripts/lib/machine-profile.mjs +3 -17
  50. package/scripts/lib/mcp-filter.mjs +109 -53
  51. package/scripts/lib/mcp-health.mjs +2 -2
  52. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  53. package/scripts/lib/psmux-info.mjs +11 -59
  54. package/scripts/lib/skill-template.mjs +0 -245
  55. package/hub/team/build-worker-prompt.mjs +0 -92
  56. package/hub/team/check-mcp-hub.mjs +0 -49
  57. package/hub/team/claude-native-bridge.mjs +0 -987
  58. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  59. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  60. package/hub/team/cli/services/native-control.mjs +0 -145
  61. package/hub/team/codex-review.mjs +0 -520
  62. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  63. package/hub/team/conductor.mjs +0 -1529
  64. package/hub/team/consensus-meta.mjs +0 -176
  65. package/hub/team/cto-pull.mjs +0 -31
  66. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  67. package/hub/team/dashboard.mjs +0 -322
  68. package/hub/team/event-log.mjs +0 -84
  69. package/hub/team/execution-mode.mjs +0 -324
  70. package/hub/team/extract-completion-payload.mjs +0 -110
  71. package/hub/team/headless-bridge-session.mjs +0 -17
  72. package/hub/team/health-probe.mjs +0 -435
  73. package/hub/team/interactive-native-launcher.mjs +0 -119
  74. package/hub/team/interactive-native-worker.mjs +0 -80
  75. package/hub/team/interactive-tui-transport.mjs +0 -479
  76. package/hub/team/launcher-template.mjs +0 -144
  77. package/hub/team/mcp-selector.mjs +0 -145
  78. package/hub/team/native-supervisor.mjs +0 -460
  79. package/hub/team/native.mjs +0 -727
  80. package/hub/team/recovery-store.mjs +0 -149
  81. package/hub/team/remote-probe.mjs +0 -292
  82. package/hub/team/remote-watcher.mjs +0 -506
  83. package/hub/team/runtime-strategy.mjs +0 -200
  84. package/hub/team/schemas/worker-completion.json +0 -39
  85. package/hub/team/sentinel-capture.mjs +0 -127
  86. package/hub/team/swarm-cli.mjs +0 -390
  87. package/hub/team/swarm-hypervisor.mjs +0 -2206
  88. package/hub/team/swarm-locks.mjs +0 -335
  89. package/hub/team/swarm-planner.mjs +0 -371
  90. package/hub/team/swarm-preflight.mjs +0 -366
  91. package/hub/team/swarm-reconciler.mjs +0 -242
  92. package/hub/team/tui-remote-adapter.mjs +0 -377
  93. package/hub/team/worker-completion-validator.mjs +0 -79
  94. package/hub/team/worktree-lifecycle.mjs +0 -639
  95. package/hub/team/wt-templates.json +0 -43
  96. package/hub/workers/antigravity-route-worker.mjs +0 -179
  97. package/scripts/lib/dynamic-route-cli.mjs +0 -107
package/cto/brief.mjs CHANGED
@@ -27,13 +27,6 @@ function formatGoal(goal) {
27
27
  return `- ${id} ${status}: ${title}`;
28
28
  }
29
29
 
30
- function formatShard(shard) {
31
- const id = oneLine(shard?.id || shard?.name || shard?.sessionId, "shard");
32
- const status = oneLine(shard?.status || shard?.state, "unknown");
33
- const detail = oneLine(shard?.summary || shard?.objective || shard?.task, "");
34
- return detail ? `- ${id} ${status}: ${detail}` : `- ${id} ${status}`;
35
- }
36
-
37
30
  function formatEvent(event) {
38
31
  const ts = oneLine(event?.ts, "unknown_ts");
39
32
  const name = oneLine(event?.event, "event");
@@ -61,9 +54,6 @@ export function renderBrief(current) {
61
54
  const activeGoals = Array.isArray(summary.active_goals)
62
55
  ? summary.active_goals.slice(0, 3)
63
56
  : [];
64
- const swarmShards = Array.isArray(summary.swarm_shards)
65
- ? summary.swarm_shards.slice(0, 2)
66
- : [];
67
57
  const recentEvents = Array.isArray(current?.ledger_tail)
68
58
  ? current.ledger_tail.slice(-2)
69
59
  : [];
@@ -75,8 +65,6 @@ export function renderBrief(current) {
75
65
  `summary: ${oneLine(summary.repo_state, "no repo summary")}`,
76
66
  "active_goals",
77
67
  ...(activeGoals.length > 0 ? activeGoals.map(formatGoal) : ["- none"]),
78
- "swarm_shards",
79
- ...(swarmShards.length > 0 ? swarmShards.map(formatShard) : ["- none"]),
80
68
  "hub_status",
81
69
  `status: ${oneLine(hub.status, "unknown")} available: ${hub.available === true} detail: ${compactDetail(hub.detail)}`,
82
70
  "recent_events",
package/cto/collect.mjs CHANGED
@@ -33,12 +33,6 @@ const SOURCE_REGISTRY = [
33
33
  probe: ".triflux/hub/status.json or host tfx-hub cache",
34
34
  enabled: true,
35
35
  },
36
- {
37
- id: "tfx_swarm",
38
- kind: "triflux-runtime-artifact",
39
- probe: ".triflux/swarm-locks.json or .triflux/swarm/*.json",
40
- enabled: true,
41
- },
42
36
  {
43
37
  id: "tfx_team",
44
38
  kind: "triflux-runtime-artifact",
@@ -306,70 +300,6 @@ function collectUltragoal(dirPath, rootDir, collectedAt) {
306
300
  }
307
301
  }
308
302
 
309
- export function collectSwarm(rootDir, collectedAt) {
310
- const locksPath = join(rootDir, ".triflux", "swarm-locks.json");
311
- const logsDir = join(rootDir, ".triflux", "swarm-logs");
312
- if (!existsSync(locksPath) && !existsSync(logsDir)) {
313
- return missingSource(
314
- "no_shell_readable_artifact",
315
- "no swarm locks or logs found",
316
- collectedAt,
317
- );
318
- }
319
- try {
320
- const detail = { locks_path: null, locks: null, shards: [], log_runs: [] };
321
- if (existsSync(locksPath)) {
322
- const locks = readJson(locksPath);
323
- detail.locks_path = relPath(rootDir, locksPath);
324
- detail.locks = safeSummary(locks);
325
- if (Array.isArray(locks?.shards)) detail.shards = locks.shards;
326
- else if (Array.isArray(locks)) detail.shards = locks;
327
- else if (locks && typeof locks === "object") {
328
- // 실제 producer(hub/team/swarm-locks.mjs persist)는
329
- // { "<file>": { workerId, leaseType, sessionMeta } } 형태의 lock map 을 쓴다.
330
- // workerId 가 곧 shard.name 이다 — swarm-hypervisor 가
331
- // acquire(shard.name, shard.files) 로 잠그기 때문(swarm-hypervisor.mjs:753).
332
- // workerId 별로 묶어 active shard row 로 환원한다. (expired lock 정밀 필터는
333
- // hub snapshot() 의 책임이라, 여기서는 persist 된 보유 lock 전부를 active 로 본다.)
334
- const byWorker = new Map();
335
- for (const entry of Object.values(locks)) {
336
- const workerId =
337
- entry && typeof entry.workerId === "string" ? entry.workerId : null;
338
- if (!workerId) continue;
339
- let row = byWorker.get(workerId);
340
- if (!row) {
341
- row = { shard_name: workerId, phase: "active", members: [] };
342
- byWorker.set(workerId, row);
343
- }
344
- const host = entry?.sessionMeta?.host;
345
- if (typeof host === "string" && host && !row.members.includes(host)) {
346
- row.members.push(host);
347
- }
348
- }
349
- detail.shards = [...byWorker.values()];
350
- }
351
- }
352
- if (existsSync(logsDir)) {
353
- detail.log_runs = readdirSync(logsDir, { withFileTypes: true })
354
- .filter((entry) => entry.isDirectory() && entry.name.startsWith("run-"))
355
- .map((entry) => {
356
- const path = join(logsDir, entry.name);
357
- return { id: entry.name, mtime: statSync(path).mtime.toISOString() };
358
- })
359
- .sort((a, b) => b.mtime.localeCompare(a.mtime))
360
- .slice(0, 5);
361
- }
362
- return sourceState(true, "ok", detail, collectedAt);
363
- } catch (error) {
364
- return sourceState(
365
- true,
366
- "read_error",
367
- error?.message || "swarm read failed",
368
- collectedAt,
369
- );
370
- }
371
- }
372
-
373
303
  function collectHandoffs(rootDir, collectedAt) {
374
304
  const dirPath = join(rootDir, ".omx", "handoffs");
375
305
  const files = listDirFiles(dirPath, rootDir, 10);
@@ -392,7 +322,6 @@ function buildSummary(current) {
392
322
  const repo = current.repo;
393
323
  const omxGoals = current.sources.ultragoal_omx.detail?.active_goals || [];
394
324
  const omcGoals = current.sources.ultragoal_omc.detail?.active_goals || [];
395
- const swarmShards = current.sources.tfx_swarm.detail?.shards || [];
396
325
  const availableSources = Object.values(current.sources).filter(
397
326
  (source) => source.available,
398
327
  ).length;
@@ -400,7 +329,6 @@ function buildSummary(current) {
400
329
  return {
401
330
  repo_state: `branch ${repo.branch || "unknown"} at ${repo.head || "unknown"} is ${repo.dirty ? "dirty" : "clean"}`,
402
331
  active_goals: [...omxGoals, ...omcGoals].slice(0, 5),
403
- swarm_shards: Array.isArray(swarmShards) ? swarmShards.slice(0, 5) : [],
404
332
  hub_status: current.sources.tfx_hub.status,
405
333
  available_sources: availableSources,
406
334
  missing_sources: SOURCE_IDS.length - availableSources,
@@ -581,7 +509,6 @@ function collectSources(rootDir, collectedAt, execFileSyncFn, opts = {}) {
581
509
  rootDir,
582
510
  collectedAt,
583
511
  ),
584
- tfx_swarm: collectSwarm(rootDir, collectedAt),
585
512
  tfx_team: collectJsonArtifact(
586
513
  "tfx_team",
587
514
  [
@@ -49,7 +49,6 @@
49
49
  "required": [
50
50
  "git",
51
51
  "tfx_hub",
52
- "tfx_swarm",
53
52
  "tfx_team",
54
53
  "tfx_synapse",
55
54
  "ultragoal_omx",
@@ -63,9 +62,6 @@
63
62
  "tfx_hub": {
64
63
  "$ref": "#/$defs/sourceState"
65
64
  },
66
- "tfx_swarm": {
67
- "$ref": "#/$defs/sourceState"
68
- },
69
65
  "tfx_team": {
70
66
  "$ref": "#/$defs/sourceState"
71
67
  },
package/cto/status.mjs CHANGED
@@ -73,30 +73,6 @@ function redactedCwdFields(cwd) {
73
73
  };
74
74
  }
75
75
 
76
- function normalizeActiveShard(shard) {
77
- return {
78
- shard_name:
79
- typeof shard?.shard_name === "string"
80
- ? shard.shard_name
81
- : typeof shard?.shardName === "string"
82
- ? shard.shardName
83
- : typeof shard?.name === "string"
84
- ? shard.name
85
- : typeof shard?.id === "string"
86
- ? shard.id
87
- : null,
88
- phase:
89
- typeof shard?.phase === "string"
90
- ? shard.phase
91
- : typeof shard?.status === "string"
92
- ? shard.status
93
- : "active",
94
- members: Array.isArray(shard?.members)
95
- ? shard.members.map((member) => String(member))
96
- : [],
97
- };
98
- }
99
-
100
76
  function normalizeSynapseOverlay(value) {
101
77
  const sessions = Array.isArray(value)
102
78
  ? value
@@ -105,11 +81,6 @@ function normalizeSynapseOverlay(value) {
105
81
  : Array.isArray(value?.live_sessions)
106
82
  ? value.live_sessions
107
83
  : [];
108
- const activeShards = Array.isArray(value?.active_shards)
109
- ? value.active_shards
110
- : Array.isArray(value?.activeShards)
111
- ? value.activeShards
112
- : [];
113
84
 
114
85
  return {
115
86
  live_sessions: sessions
@@ -119,7 +90,6 @@ function normalizeSynapseOverlay(value) {
119
90
  ...redactedCwdFields(session?.cwd),
120
91
  }))
121
92
  .filter((session) => session.sessionId),
122
- active_shards: activeShards.map(normalizeActiveShard),
123
93
  };
124
94
  }
125
95
 
@@ -150,28 +120,10 @@ async function readSynapseOverlay(opts) {
150
120
  );
151
121
  return normalizeSynapseOverlay(raw);
152
122
  } catch {
153
- return { live_sessions: [], active_shards: [] };
123
+ return { live_sessions: [] };
154
124
  }
155
125
  }
156
126
 
157
- // active_shards 의 source 는 live_sessions 와 다르다: live_sessions 는 synapse
158
- // overlay(실시간 세션)에서, active_shards 는 collect 가 .triflux/swarm-locks.json 을
159
- // snapshot 한 current.json(sources.tfx_swarm.detail.shards)에서 온다. synapse
160
- // overlay 가 shard 를 채우는 경로가 생기면 그쪽을 우선하고, 없으면 snapshot 으로
161
- // fallback 한다(현재 overlay 의 active_shards 는 항상 비어 snapshot 을 쓴다).
162
- function deriveActiveShards(current, overlay) {
163
- // overlay(synapse)가 채운 shard 중 식별 가능한 것만 authoritative 로 본다.
164
- // normalizeSynapseOverlay 가 shard_name 을 null 로 정규화한 무효 행이 snapshot
165
- // fallback 을 가로채지 않도록 먼저 거른다.
166
- const overlayShards = Array.isArray(overlay?.active_shards)
167
- ? overlay.active_shards.filter((shard) => shard?.shard_name)
168
- : [];
169
- if (overlayShards.length > 0) return overlayShards;
170
- const shards = current?.sources?.tfx_swarm?.detail?.shards;
171
- if (!Array.isArray(shards)) return [];
172
- return shards.map(normalizeActiveShard).filter((shard) => shard.shard_name);
173
- }
174
-
175
127
  function deriveLiveSessionGroups(liveSessions) {
176
128
  const groups = new Map();
177
129
  for (const session of liveSessions || []) {
@@ -202,7 +154,7 @@ function projectStatus(current, overlay) {
202
154
  ledger_tail: Array.isArray(current?.ledger_tail) ? current.ledger_tail : [],
203
155
  live_sessions: liveSessions,
204
156
  live_session_groups: deriveLiveSessionGroups(liveSessions),
205
- active_shards: deriveActiveShards(current, overlay),
157
+ active_shards: [],
206
158
  hygiene: compactHygieneCounts(projectCtoHygiene({ current, overlay })),
207
159
  };
208
160
  }
package/hub/index.mjs CHANGED
@@ -11,11 +11,3 @@ export { createPipeServer, getPipePath } from './pipe.mjs';
11
11
  // Team
12
12
  export { orchestrate, decomposeTask, buildLeadPrompt, buildPrompt } from './team/orchestrator.mjs';
13
13
  export { createPsmuxSession, startCapture } from './team/psmux.mjs';
14
- export { createConductor } from './team/conductor.mjs';
15
-
16
- // Swarm
17
- export { createSwarmLocks } from './team/swarm-locks.mjs';
18
- export { parseShards, buildFileLeaseMap, buildMcpManifest, computeMergeOrder, planSwarm } from './team/swarm-planner.mjs';
19
- export { createSwarmHypervisor, SWARM_STATES } from './team/swarm-hypervisor.mjs';
20
- export { reconcile, buildRedundantIds, shouldRunRedundant } from './team/swarm-reconciler.mjs';
21
- export { ensureWorktree, prepareIntegrationBranch, rebaseShardOntoIntegration, pruneWorktree, fetchRemoteShard } from './team/worktree-lifecycle.mjs';
@@ -23,7 +23,6 @@
23
23
  "test-engineer": "codex",
24
24
  "qa-tester": "codex",
25
25
  "codex": "codex",
26
- "gemini": "antigravity",
27
26
  "antigravity": "antigravity",
28
27
  "agy": "antigravity",
29
28
  "claude": "claude"
package/hub/team/ansi.mjs CHANGED
@@ -21,9 +21,6 @@ export function moveTo(row, col) {
21
21
  export function moveUp(n = 1) {
22
22
  return `${ESC}[${n}A`;
23
23
  }
24
- export function moveDown(n = 1) {
25
- return `${ESC}[${n}B`;
26
- }
27
24
 
28
25
  // ── 줄 제어 ──
29
26
  export const clearLine = `${ESC}[2K`;
@@ -47,7 +44,7 @@ export const FG = {
47
44
  gray: `${ESC}[90m`,
48
45
  // triflux 브랜드
49
46
  codex: `${ESC}[38;2;16;163;127m`, // #10a37f codex green
50
- gemini: `${ESC}[38;5;39m`, // blue
47
+ antigravity: `${ESC}[38;5;39m`, // blue
51
48
  claude: `${ESC}[38;2;232;112;64m`, // orange
52
49
  triflux: `${ESC}[38;5;214m`, // amber
53
50
  accent: `${ESC}[38;5;75m`, // light blue (Catppuccin blue)
@@ -439,40 +436,9 @@ export function progressBar(percent, width = 20, time) {
439
436
  return `${fillText}${emptyText}`;
440
437
  }
441
438
 
442
- // ── 애니메이션 진행률 바 (shimmer sweep) ──
443
- export function animatedProgressBar(percent, width = 20, tick = 0) {
444
- const ratio = Math.max(0, Math.min(100, percent)) / 100;
445
- const filled = Math.round(ratio * width);
446
- const empty = width - filled;
447
- if (filled === 0 || percent >= 100) return progressBar(percent, width);
448
- const baseClr = percent >= 50 ? MOCHA.partial : MOCHA.fail;
449
- const pos = tick % (filled + 3);
450
- let bar = "";
451
- for (let i = 0; i < filled; i++) {
452
- const d = Math.abs(i - pos);
453
- if (d === 0) bar += `${ESC}[97m█`;
454
- else if (d === 1) bar += `${baseClr}▓`;
455
- else bar += `${baseClr}█`;
456
- }
457
- return `${bar}${MOCHA.border}${"░".repeat(empty)}${RESET}`;
458
- }
459
-
460
- // ── 상태 아이콘 ──
461
439
  export const STATUS_ICON = {
462
440
  running: `${MOCHA.partial}⏳${RESET}`,
463
441
  completed: `${MOCHA.ok}✓${RESET}`,
464
442
  failed: `${MOCHA.fail}✗${RESET}`,
465
443
  pending: `${FG.gray}⏸${RESET}`,
466
444
  };
467
-
468
- export const CLI_ICON = {
469
- codex: `${FG.codex}⚪${RESET}`,
470
- gemini: `${FG.gemini}🔵${RESET}`,
471
- claude: `${FG.claude}🟠${RESET}`,
472
- };
473
-
474
- // ── 로딩 도트 (braille spinner) ──
475
- const BRAILLE_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
476
- export function loadingDots(tick = 0, clr = MOCHA.thinking) {
477
- return `${clr}${BRAILLE_FRAMES[tick % BRAILLE_FRAMES.length]}${RESET}`;
478
- }
@@ -1,153 +1,17 @@
1
- // hub/team/backend.mjs — CLI 백엔드 추상화 레이어
2
- // 각 CLI(codex/antigravity/claude)의 명령 빌드 로직을 클래스로 캡슐화한다.
3
- // v7.2.2
4
- import { writeFileSync } from "node:fs";
5
- import { createRequire } from "node:module";
6
-
7
- import { buildExecArgs } from "@triflux/core/hub/codex-adapter.mjs";
8
1
  import { IS_WINDOWS } from "@triflux/core/hub/platform.mjs";
2
+ import { powershellSingleQuote, shellQuote } from "./terminal-opener.mjs";
9
3
 
10
- export function buildGeminiCommand(prompt, resultFile, { isWindows } = {}) {
11
- return buildAntigravityCommand(prompt, resultFile, { isWindows });
12
- }
13
-
14
- export function buildAntigravityCommand(
15
- prompt,
16
- resultFile,
17
- { isWindows } = {},
18
- ) {
19
- // Persist prompt to a sibling file so the shell never interpolates raw
20
- // prompt content. Without this the Unix branch let `$()`, backticks, `\\`,
21
- // and `"` reach the shell verbatim (P1: shell injection).
22
- const promptFile = `${resultFile}.prompt`;
23
- writeFileSync(promptFile, prompt);
24
- // agy 1.1.27 부터 --print 는 값(프롬프트)이 필수이고 stdin 프롬프트는 거부된다.
25
- // 파일 내용을 셸이 재해석하지 않는 형태(따옴표 안 명령 치환 / Get-Content 식)로 값에 넣는다.
26
- if (isWindows) {
27
- return `agy --dangerously-skip-permissions --print (Get-Content -Raw '${promptFile}') > '${resultFile}' 2>'${resultFile}.err'`;
28
- }
29
- return `agy --dangerously-skip-permissions --print "$(cat '${promptFile}')" > '${resultFile}' 2>'${resultFile}.err'`;
30
- }
31
-
32
- const _require = createRequire(import.meta.url);
33
-
34
- // ── 백엔드 클래스 ──────────────────────────────────────────────────────────
35
-
36
- export class CodexBackend {
37
- name() {
38
- return "codex";
39
- }
40
- command() {
41
- return "codex";
42
- }
43
-
44
- /**
45
- * @param {string} prompt — 프롬프트 (프롬프트 파일 경로가 아닌 PowerShell 표현식)
46
- * @param {string} resultFile — 결과 저장 경로
47
- * @param {object} [opts]
48
- * @returns {string} PowerShell 명령 (cls 제외)
49
- */
50
- buildArgs(prompt, resultFile, opts = {}) {
51
- return buildExecArgs({ prompt, resultFile, ...opts });
52
- }
53
-
54
- env() {
55
- return {};
56
- }
57
- }
58
-
59
- export class GeminiBackend {
60
- name() {
61
- return "gemini";
62
- }
63
- command() {
64
- return "agy";
65
- }
66
-
67
- buildArgs(prompt, resultFile, opts = {}) {
68
- return buildGeminiCommand(prompt, resultFile, { isWindows: IS_WINDOWS });
69
- }
70
-
71
- env() {
72
- return {};
4
+ class ClaudeBackend {
5
+ buildArgs(prompt, resultFile, { isWindows = IS_WINDOWS } = {}) {
6
+ const quote = isWindows ? powershellSingleQuote : shellQuote;
7
+ const args = ["--print", prompt, "--output-format", "text"];
8
+ return `claude ${args.map(quote).join(" ")} > ${quote(resultFile)} 2>&1`;
73
9
  }
74
10
  }
75
11
 
76
- export class ClaudeBackend {
77
- name() {
78
- return "claude";
79
- }
80
- command() {
81
- return "claude";
82
- }
83
-
84
- buildArgs(prompt, resultFile, opts = {}) {
85
- return `claude --print ${prompt} --output-format text > '${resultFile}' 2>&1`;
86
- }
12
+ const claude = new ClaudeBackend();
87
13
 
88
- env() {
89
- return {};
90
- }
91
- }
92
-
93
- export class AntigravityBackend {
94
- name() {
95
- return "antigravity";
96
- }
97
- command() {
98
- return "agy";
99
- }
100
-
101
- buildArgs(prompt, resultFile, opts = {}) {
102
- return buildAntigravityCommand(prompt, resultFile, {
103
- isWindows: IS_WINDOWS,
104
- ...opts,
105
- });
106
- }
107
-
108
- env() {
109
- return {};
110
- }
111
- }
112
-
113
- // ── 레지스트리 ─────────────────────────────────────────────────────────────
114
-
115
- /** @type {Map<string, CodexBackend|GeminiBackend|ClaudeBackend|AntigravityBackend>} */
116
- const backends = new Map([
117
- ["codex", new CodexBackend()],
118
- ["gemini", new GeminiBackend()],
119
- ["claude", new ClaudeBackend()],
120
- ["antigravity", new AntigravityBackend()],
121
- ]);
122
-
123
- /**
124
- * 백엔드 이름으로 조회한다.
125
- * @param {string} name — "codex" | "gemini" | "claude" | "antigravity"
126
- * @returns {CodexBackend|GeminiBackend|ClaudeBackend|AntigravityBackend}
127
- * @throws {Error} 등록되지 않은 이름
128
- */
129
14
  export function getBackend(name) {
130
- const b = backends.get(name);
131
- if (!b) throw new Error(`지원하지 않는 CLI: ${name}`);
132
- return b;
133
- }
134
-
135
- /**
136
- * 에이전트명 또는 CLI명을 Backend로 해석한다.
137
- * agent-map.json을 통해 에이전트명 → CLI명으로 변환 후 레지스트리에서 조회한다.
138
- * @param {string} agentOrCli — "executor", "codex", "designer" 등
139
- * @returns {CodexBackend|GeminiBackend|ClaudeBackend|AntigravityBackend}
140
- */
141
- export function getBackendForAgent(agentOrCli) {
142
- const agentMap = _require("./agent-map.json");
143
- const cliName = agentMap[agentOrCli] || agentOrCli;
144
- return getBackend(cliName);
145
- }
146
-
147
- /**
148
- * 등록된 모든 백엔드를 반환한다.
149
- * @returns {Array<CodexBackend|GeminiBackend|ClaudeBackend|AntigravityBackend>}
150
- */
151
- export function listBackends() {
152
- return Array.from(backends.values());
15
+ if (name !== "claude") throw new Error(`지원하지 않는 CLI: ${name}`);
16
+ return claude;
153
17
  }
@@ -6,11 +6,7 @@ import {
6
6
  launchAttachInWindowsTerminal,
7
7
  wantsWtAttachFallback,
8
8
  } from "../services/attach-fallback.mjs";
9
- import {
10
- isNativeMode,
11
- isTeamAlive,
12
- isWtMode,
13
- } from "../services/runtime-mode.mjs";
9
+ import { isTeamAlive } from "../services/runtime-mode.mjs";
14
10
  import { loadTeamState } from "../services/state-store.mjs";
15
11
 
16
12
  export async function teamAttach(args = []) {
@@ -19,18 +15,6 @@ export async function teamAttach(args = []) {
19
15
  console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
20
16
  return;
21
17
  }
22
- if (isNativeMode(state)) {
23
- console.log(
24
- `\n ${DIM}in-process 모드는 별도 attach가 없습니다.${RESET}\n ${DIM}상태 확인: tfx multi status${RESET}\n`,
25
- );
26
- return;
27
- }
28
- if (isWtMode(state)) {
29
- console.log(
30
- `\n ${DIM}wt 모드는 attach 개념이 없습니다 (Windows Terminal pane가 독립 실행됨).${RESET}\n ${DIM}재실행/정리는: tfx multi stop${RESET}\n`,
31
- );
32
- return;
33
- }
34
18
 
35
19
  try {
36
20
  attachSession(state.sessionName);
@@ -1,18 +1,15 @@
1
1
  import { injectPrompt, sendKeys } from "../../pane.mjs";
2
- import { DIM, RESET, WHITE, YELLOW } from "../../shared.mjs";
3
- import { ok, warn } from "../render.mjs";
4
- import { publishLeadControl } from "../services/hub-client.mjs";
2
+ import { DIM, RESET, WHITE } from "../../shared.mjs";
3
+ import { ok } from "../render.mjs";
5
4
  import { resolveMember } from "../services/member-selector.mjs";
6
- import { nativeRequest } from "../services/native-control.mjs";
7
- import {
8
- isNativeMode,
9
- isTeamAlive,
10
- isWtMode,
11
- } from "../services/runtime-mode.mjs";
5
+ import { isTeamAlive } from "../services/runtime-mode.mjs";
12
6
  import { loadTeamState } from "../services/state-store.mjs";
13
7
 
14
8
  export async function teamControl(args = []) {
15
9
  const state = loadTeamState();
10
+ if (state?.teammateMode === "headless") {
11
+ throw new Error("headless 실행에는 지원하지 않는다");
12
+ }
16
13
  if (!state || !isTeamAlive(state)) {
17
14
  console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
18
15
  return;
@@ -30,35 +27,12 @@ export async function teamControl(args = []) {
30
27
  );
31
28
  return;
32
29
  }
33
- if (isWtMode(state)) {
34
- console.log(
35
- `\n ${YELLOW}⚠${RESET} wt 모드는 Hub direct/control 주입 경로가 비활성입니다.\n ${DIM}수동 제어: 해당 pane에서 직접 명령/인터럽트를 수행하세요.${RESET}\n`,
36
- );
37
- return;
38
- }
39
-
40
- let directOk = false;
41
- if (isNativeMode(state)) {
42
- directOk = !!(
43
- await nativeRequest(state, "/control", {
44
- member: member.name,
45
- command,
46
- reason,
47
- })
48
- )?.ok;
49
- } else {
50
- injectPrompt(
51
- member.pane,
52
- `[LEAD CONTROL] command=${command}${reason ? ` reason=${reason}` : ""}`,
53
- );
54
- if (command === "interrupt") sendKeys(member.pane, "C-c");
55
- directOk = true;
56
- }
30
+ injectPrompt(
31
+ member.pane,
32
+ `[LEAD CONTROL] command=${command}${reason ? ` reason=${reason}` : ""}`,
33
+ );
34
+ if (command === "interrupt") sendKeys(member.pane, "C-c");
57
35
 
58
- const published = await publishLeadControl(state, member, command, reason);
59
- if (directOk && published)
60
- ok(`${member.name} 제어 전송 (${command}, direct + hub)`);
61
- else if (directOk) ok(`${member.name} 제어 전송 (${command}, direct only)`);
62
- else warn(`${member.name} 제어 전송 실패 (${command})`);
36
+ ok(`${member.name} 제어 전송 (${command})`);
63
37
  console.log("");
64
38
  }
@@ -2,18 +2,10 @@ import {
2
2
  capturePaneOutput,
3
3
  detectMultiplexer,
4
4
  getSessionAttachedCount,
5
- hasWindowsTerminal,
6
- hasWindowsTerminalSession,
7
5
  listSessions,
8
6
  } from "../../session.mjs";
9
7
  import { AMBER, BOLD, DIM, RESET } from "../../shared.mjs";
10
- import { formatCompletionSuffix } from "../render.mjs";
11
- import { getHubInfo, nativeGetStatus } from "../services/hub-client.mjs";
12
- import {
13
- isNativeMode,
14
- isTeamAlive,
15
- isWtMode,
16
- } from "../services/runtime-mode.mjs";
8
+ import { isTeamAlive } from "../services/runtime-mode.mjs";
17
9
  import { loadTeamState, TEAM_PROFILE } from "../services/state-store.mjs";
18
10
 
19
11
  export async function teamDebug(args = []) {
@@ -23,7 +15,6 @@ export async function teamDebug(args = []) {
23
15
  flagIndex === -1
24
16
  ? 20
25
17
  : Math.max(3, parseInt(args[flagIndex + 1] || "20", 10) || 20);
26
- const hub = await getHubInfo();
27
18
 
28
19
  console.log(`\n ${AMBER}${BOLD}⬡ Team Debug${RESET}\n`);
29
20
  console.log(` platform: ${process.platform}`);
@@ -32,8 +23,6 @@ export async function teamDebug(args = []) {
32
23
  ` tty: stdout=${!!process.stdout.isTTY}, stdin=${!!process.stdin.isTTY}`,
33
24
  );
34
25
  console.log(` mux: ${detectMultiplexer() || "none"}`);
35
- console.log(` hub-pid: ${hub ? `${hub.pid}` : "-"}`);
36
- console.log(` hub-url: ${hub?.url || "-"}`);
37
26
  const sessions = listSessions();
38
27
  console.log(` sessions: ${sessions.length ? sessions.join(", ") : "-"}`);
39
28
 
@@ -53,31 +42,6 @@ export async function teamDebug(args = []) {
53
42
  ` attached: ${getSessionAttachedCount(state.sessionName) ?? "-"}`,
54
43
  );
55
44
 
56
- if (isWtMode(state)) {
57
- console.log(`\n ${BOLD}wt-session${RESET}`);
58
- console.log(` window: ${state?.wt?.windowId ?? 0}`);
59
- console.log(` layout: ${state?.wt?.layout || state?.layout || "-"}`);
60
- console.log(
61
- ` panes: ${state?.wt?.paneCount ?? (state.members || []).length}`,
62
- );
63
- console.log(` wt.exe: ${hasWindowsTerminal() ? "yes" : "no"}`);
64
- console.log(` WT_SESSION:${hasWindowsTerminalSession() ? "yes" : "no"}`);
65
- console.log("");
66
- return;
67
- }
68
-
69
- if (isNativeMode(state)) {
70
- console.log(`\n ${BOLD}native-members${RESET}`);
71
- const members = (await nativeGetStatus(state))?.data?.members || [];
72
- if (!members.length) console.log(` ${DIM}(no data)${RESET}`);
73
- for (const member of members)
74
- console.log(
75
- ` - ${member.name}: ${member.status}${formatCompletionSuffix(member)}${member.lastPreview ? ` ${DIM}${member.lastPreview}${RESET}` : ""}`,
76
- );
77
- console.log("");
78
- return;
79
- }
80
-
81
45
  console.log(
82
46
  `\n ${BOLD}pane-tail${RESET} ${DIM}(last ${lines} lines)${RESET}`,
83
47
  );