@triflux/remote 10.1.0 → 10.25.1

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 (155) hide show
  1. package/hub/pipe.mjs +239 -121
  2. package/hub/public/dashboard.html +61 -0
  3. package/hub/server.mjs +2165 -708
  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 +84 -17
  9. package/hub/team/build-worker-prompt.mjs +42 -0
  10. package/hub/team/check-mcp-hub.mjs +49 -0
  11. package/hub/team/claude-daemon-control.mjs +219 -0
  12. package/hub/team/claude-native-bridge.mjs +784 -0
  13. package/hub/team/claude-session-projection.mjs +65 -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 +154 -33
  23. package/hub/team/cli/commands/start/parse-args.mjs +89 -12
  24. package/hub/team/cli/commands/start/start-headless.mjs +125 -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/index.mjs +2 -2
  33. package/hub/team/cli/manifest.mjs +38 -8
  34. package/hub/team/cli/render.mjs +52 -30
  35. package/hub/team/cli/services/attach-fallback.mjs +74 -54
  36. package/hub/team/cli/services/hub-client.mjs +44 -16
  37. package/hub/team/cli/services/member-selector.mjs +37 -30
  38. package/hub/team/cli/services/native-control.mjs +144 -116
  39. package/hub/team/cli/services/runtime-mode.mjs +55 -1
  40. package/hub/team/cli/services/state-store.mjs +66 -9
  41. package/hub/team/cli/services/task-model.mjs +40 -30
  42. package/hub/team/codex-review.mjs +521 -0
  43. package/hub/team/conductor-mesh-bridge.mjs +25 -24
  44. package/hub/team/conductor-registry.mjs +121 -0
  45. package/hub/team/conductor.mjs +1379 -671
  46. package/hub/team/consensus-meta.mjs +176 -0
  47. package/hub/team/dashboard-anchor.mjs +4 -5
  48. package/hub/team/dashboard-layout.mjs +3 -1
  49. package/hub/team/dashboard-open.mjs +20 -118
  50. package/hub/team/dashboard.mjs +76 -28
  51. package/hub/team/event-log.mjs +18 -10
  52. package/hub/team/execution-mode.mjs +241 -0
  53. package/hub/team/extract-completion-payload.mjs +110 -0
  54. package/hub/team/git-preflight.mjs +286 -0
  55. package/hub/team/handoff.mjs +49 -18
  56. package/hub/team/headless-bridge-session.mjs +17 -0
  57. package/hub/team/headless.mjs +2065 -1156
  58. package/hub/team/health-probe.mjs +232 -69
  59. package/hub/team/launcher-template.mjs +23 -13
  60. package/hub/team/lead-control.mjs +91 -4
  61. package/hub/team/mcp-selector.mjs +145 -0
  62. package/hub/team/native-supervisor.mjs +90 -26
  63. package/hub/team/native.mjs +74 -35
  64. package/hub/team/nativeProxy.mjs +184 -113
  65. package/hub/team/notify.mjs +147 -78
  66. package/hub/team/orchestrator.mjs +173 -161
  67. package/hub/team/pane.mjs +43 -15
  68. package/hub/team/process-cleanup.mjs +69 -25
  69. package/hub/team/psmux.mjs +1020 -279
  70. package/hub/team/recovery-store.mjs +149 -0
  71. package/hub/team/remote-probe.mjs +68 -52
  72. package/hub/team/remote-session.mjs +131 -59
  73. package/hub/team/remote-watcher.mjs +61 -33
  74. package/hub/team/retry-state-machine.mjs +267 -0
  75. package/hub/team/routing.mjs +51 -25
  76. package/hub/team/runtime-strategy.mjs +200 -0
  77. package/hub/team/schemas/worker-completion.json +39 -0
  78. package/hub/team/sentinel-capture.mjs +127 -0
  79. package/hub/team/session-sync.mjs +153 -6
  80. package/hub/team/session.mjs +605 -611
  81. package/hub/team/shared.mjs +13 -13
  82. package/hub/team/staleState.mjs +72 -30
  83. package/hub/team/swarm-cli.mjs +390 -0
  84. package/hub/team/swarm-hypervisor.mjs +1522 -193
  85. package/hub/team/swarm-intent.mjs +216 -0
  86. package/hub/team/swarm-locks.mjs +140 -25
  87. package/hub/team/swarm-planner.mjs +121 -21
  88. package/hub/team/swarm-preflight.mjs +317 -0
  89. package/hub/team/swarm-reconciler.mjs +128 -23
  90. package/hub/team/synapse-cli.mjs +458 -0
  91. package/hub/team/synapse-http.mjs +60 -0
  92. package/hub/team/synapse-registry.mjs +329 -0
  93. package/hub/team/terminal-opener.mjs +216 -0
  94. package/hub/team/tui-core.mjs +288 -0
  95. package/hub/team/tui-lite.mjs +234 -146
  96. package/hub/team/tui-remote-adapter.mjs +18 -34
  97. package/hub/team/tui-synapse.mjs +223 -0
  98. package/hub/team/tui-viewer.mjs +104 -44
  99. package/hub/team/tui-widgets.mjs +262 -0
  100. package/hub/team/tui.mjs +927 -437
  101. package/hub/team/worker-completion-validator.mjs +84 -0
  102. package/hub/team/worker-sandbox.mjs +93 -0
  103. package/hub/team/worker-signal.mjs +263 -0
  104. package/hub/team/worker-signal.types.d.ts +52 -0
  105. package/hub/team/worktree-lifecycle.mjs +517 -63
  106. package/hub/team/wt-manager.mjs +297 -26
  107. package/hub/tools.mjs +474 -252
  108. package/hub/tray.mjs +411 -368
  109. package/hub/workers/claude-worker.mjs +162 -118
  110. package/hub/workers/codex-app-server-worker.mjs +1015 -0
  111. package/hub/workers/codex-mcp.mjs +724 -507
  112. package/hub/workers/delegator-mcp.mjs +662 -335
  113. package/hub/workers/factory.mjs +100 -21
  114. package/hub/workers/gemini-worker.mjs +179 -86
  115. package/hub/workers/interface.mjs +23 -2
  116. package/hub/workers/lib/jsonrpc-stdio.mjs +463 -0
  117. package/hub/workers/worker-utils.mjs +22 -16
  118. package/package.json +4 -3
  119. package/scripts/lib/agent-json.mjs +27 -0
  120. package/scripts/lib/async.mjs +174 -0
  121. package/scripts/lib/claudemd-scanner.mjs +223 -0
  122. package/scripts/lib/cli-agy.mjs +62 -0
  123. package/scripts/lib/cli-claude.mjs +78 -0
  124. package/scripts/lib/cli-codex.mjs +199 -0
  125. package/scripts/lib/cli-gemini.mjs +67 -0
  126. package/scripts/lib/codex-recovery.sh +50 -0
  127. package/scripts/lib/context.mjs +67 -0
  128. package/scripts/lib/cross-review-utils.mjs +59 -0
  129. package/scripts/lib/doctor-env-checks.mjs +121 -0
  130. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  131. package/scripts/lib/env-probe.mjs +269 -0
  132. package/scripts/lib/env.mjs +14 -0
  133. package/scripts/lib/gemini-profiles.mjs +119 -0
  134. package/scripts/lib/handoff.mjs +187 -0
  135. package/scripts/lib/hook-utils.mjs +16 -0
  136. package/scripts/lib/hub.mjs +48 -0
  137. package/scripts/lib/keyword-rules.mjs +195 -0
  138. package/scripts/lib/logger.mjs +105 -0
  139. package/scripts/lib/mcp-filter.mjs +883 -0
  140. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  141. package/scripts/lib/mcp-guard-engine.mjs +2120 -0
  142. package/scripts/lib/mcp-health.mjs +773 -0
  143. package/scripts/lib/mcp-manifest.mjs +89 -0
  144. package/scripts/lib/mcp-server-catalog.mjs +355 -0
  145. package/scripts/lib/pid.mjs +63 -0
  146. package/scripts/lib/process-utils.mjs +26 -0
  147. package/scripts/lib/psmux-info.mjs +179 -0
  148. package/scripts/lib/quota.mjs +47 -0
  149. package/scripts/lib/remote-spawn-transfer.mjs +226 -0
  150. package/scripts/lib/skill-state.mjs +220 -0
  151. package/scripts/lib/skill-template.mjs +375 -0
  152. package/scripts/lib/team.mjs +75 -0
  153. package/scripts/lib/timeout.mjs +66 -0
  154. package/scripts/lib/tmp.mjs +26 -0
  155. package/scripts/lib/toml.mjs +113 -0
@@ -0,0 +1,174 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ readFileSync,
7
+ statSync,
8
+ writeFileSync,
9
+ } from "node:fs";
10
+ import { join } from "node:path";
11
+
12
+ function jobDir(jobsDir, jobId) {
13
+ return join(jobsDir, jobId);
14
+ }
15
+
16
+ function readState(dir) {
17
+ const path = join(dir, "state.json");
18
+ if (!existsSync(path)) return null;
19
+ return JSON.parse(readFileSync(path, "utf8"));
20
+ }
21
+
22
+ function writeState(dir, state) {
23
+ writeFileSync(join(dir, "state.json"), `${JSON.stringify(state, null, 2)}\n`);
24
+ }
25
+
26
+ function fileSize(path) {
27
+ try {
28
+ return statSync(path).size;
29
+ } catch {
30
+ return 0;
31
+ }
32
+ }
33
+
34
+ function isAlive(pid) {
35
+ if (!pid || pid === "starting") return false;
36
+ try {
37
+ process.kill(Number(pid), 0);
38
+ return true;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ export function createJob({
45
+ jobsDir,
46
+ command,
47
+ args = [],
48
+ env = process.env,
49
+ cwd = process.cwd(),
50
+ timeoutMs = 0,
51
+ }) {
52
+ mkdirSync(jobsDir, { recursive: true });
53
+ const id = randomUUID();
54
+ const dir = jobDir(jobsDir, id);
55
+ mkdirSync(dir, { recursive: true });
56
+ const startedAt = Date.now();
57
+ const stdoutPath = join(dir, "result.log");
58
+ const stderrPath = join(dir, "stderr.log");
59
+ writeState(dir, {
60
+ id,
61
+ state: "starting",
62
+ startedAt,
63
+ command,
64
+ args,
65
+ timeoutMs,
66
+ });
67
+ writeFileSync(join(dir, "pid"), "starting");
68
+ writeFileSync(join(dir, "start_time"), String(Math.floor(startedAt / 1000)));
69
+
70
+ const child = spawn(command, args, {
71
+ cwd,
72
+ env,
73
+ detached: true,
74
+ stdio: ["ignore", "pipe", "pipe"],
75
+ });
76
+ child.unref?.();
77
+ writeFileSync(join(dir, "pid"), String(child.pid));
78
+ writeState(dir, {
79
+ id,
80
+ state: "running",
81
+ pid: child.pid,
82
+ startedAt,
83
+ command,
84
+ args,
85
+ timeoutMs,
86
+ });
87
+ child.stdout?.on("data", (chunk) => {
88
+ writeFileSync(stdoutPath, chunk, { flag: "a" });
89
+ });
90
+ child.stderr?.on("data", (chunk) => {
91
+ writeFileSync(stderrPath, chunk, { flag: "a" });
92
+ });
93
+ let timer = null;
94
+ let timedOut = false;
95
+ if (timeoutMs > 0) {
96
+ timer = setTimeout(() => {
97
+ timedOut = true;
98
+ try {
99
+ process.kill(child.pid, "SIGTERM");
100
+ } catch {
101
+ // Process already exited.
102
+ }
103
+ }, timeoutMs);
104
+ timer.unref?.();
105
+ }
106
+ child.on("exit", (code) => {
107
+ if (timer) clearTimeout(timer);
108
+ const exitCode = timedOut ? 124 : (code ?? 1);
109
+ writeFileSync(join(dir, "exit_code"), String(exitCode));
110
+ writeFileSync(join(dir, "done"), "");
111
+ writeState(dir, {
112
+ id,
113
+ state: exitCode === 0 ? "done" : exitCode === 124 ? "timeout" : "failed",
114
+ pid: child.pid,
115
+ startedAt,
116
+ finishedAt: Date.now(),
117
+ exitCode,
118
+ command,
119
+ args,
120
+ timeoutMs,
121
+ });
122
+ });
123
+
124
+ return { id, dir, pid: child.pid };
125
+ }
126
+
127
+ export function getJobStatus(jobId, { jobsDir }) {
128
+ const dir = jobDir(jobsDir, jobId);
129
+ if (!existsSync(dir))
130
+ return { state: "error", text: "error: job not found", exitCode: 1 };
131
+ const state = readState(dir);
132
+ if (state?.state === "done") return { ...state, text: "done" };
133
+ if (state?.state === "timeout") return { ...state, text: "timeout" };
134
+ if (state?.state === "failed") return { ...state, text: "failed" };
135
+ const pidText = existsSync(join(dir, "pid"))
136
+ ? readFileSync(join(dir, "pid"), "utf8").trim()
137
+ : "";
138
+ if (pidText === "starting")
139
+ return { ...(state ?? {}), state: "starting", text: "starting" };
140
+ if (isAlive(pidText)) {
141
+ const start =
142
+ Number(readFileSync(join(dir, "start_time"), "utf8")) ||
143
+ Math.floor(Date.now() / 1000);
144
+ const elapsed = Math.max(0, Math.floor(Date.now() / 1000) - start);
145
+ const bytes = fileSize(join(dir, "result.log"));
146
+ return {
147
+ ...(state ?? {}),
148
+ state: "running",
149
+ text: `running elapsed=${elapsed}s output=${bytes}B`,
150
+ };
151
+ }
152
+ return { ...(state ?? {}), state: "failed", text: "failed" };
153
+ }
154
+
155
+ export function getJobResult(jobId, { jobsDir }) {
156
+ const dir = jobDir(jobsDir, jobId);
157
+ if (!existsSync(dir)) throw new Error("job not found");
158
+ const status = getJobStatus(jobId, { jobsDir });
159
+ if (!["done", "timeout", "failed"].includes(status.state)) {
160
+ throw new Error("job still running");
161
+ }
162
+ const stdout = existsSync(join(dir, "result.log"))
163
+ ? readFileSync(join(dir, "result.log"), "utf8")
164
+ : "";
165
+ const stderr = existsSync(join(dir, "stderr.log"))
166
+ ? readFileSync(join(dir, "stderr.log"), "utf8")
167
+ : "";
168
+ return {
169
+ state: status.state,
170
+ exitCode: status.exitCode ?? 1,
171
+ stdout: stdout || stderr,
172
+ stderr,
173
+ };
174
+ }
@@ -0,0 +1,223 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ export const TFX_START = "<!-- TFX:START -->";
7
+ export const TFX_END = "<!-- TFX:END -->";
8
+ export const OMC_END = "<!-- OMC:END -->";
9
+ const TFX_VERSION_RE = /<!-- TFX:VERSION:([\d.]+) -->/u;
10
+
11
+ const LEGACY_PATTERNS = [
12
+ /<user_cli_routing>/u,
13
+ /Codex Pro 무료 기간/u,
14
+ /codex exec --dangerously-bypass.*skip-git-repo-check/u,
15
+ /OMC 에이전트 → CLI 매핑/u,
16
+ /Spark 가드레일/u,
17
+ ];
18
+
19
+ const DEFAULT_TFX_TEMPLATE = [
20
+ "### triflux CLI routing (managed)",
21
+ "- 이 블록은 triflux setup에서 자동으로 관리됩니다.",
22
+ "- 직접 수정이 필요하면 블록 바깥에 사용자 섹션을 추가하세요.",
23
+ ].join("\n");
24
+
25
+ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
26
+ const DEFAULT_TEMPLATE_PATH = join(
27
+ SCRIPT_DIR,
28
+ "..",
29
+ "templates",
30
+ "claudemd-tfx-section.md",
31
+ );
32
+
33
+ function resolveVersion(version) {
34
+ if (version) return version;
35
+ try {
36
+ const pkgPath = join(SCRIPT_DIR, "..", "..", "package.json");
37
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
38
+ return pkg.version || "0.0.0";
39
+ } catch {
40
+ return "0.0.0";
41
+ }
42
+ }
43
+
44
+ function resolveTemplate(template, templatePath = DEFAULT_TEMPLATE_PATH) {
45
+ if (typeof template === "string" && template.trim()) {
46
+ return template.trim();
47
+ }
48
+ if (existsSync(templatePath)) {
49
+ const raw = readFileSync(templatePath, "utf8").trim();
50
+ if (raw) return raw;
51
+ }
52
+ return DEFAULT_TFX_TEMPLATE;
53
+ }
54
+
55
+ function findManagedSection(rawText) {
56
+ const raw = String(rawText || "");
57
+ const startIdx = raw.indexOf(TFX_START);
58
+ const endMarkerIdx = raw.indexOf(TFX_END);
59
+ if (startIdx === -1 || endMarkerIdx === -1 || endMarkerIdx <= startIdx) {
60
+ return {
61
+ found: false,
62
+ content: null,
63
+ version: null,
64
+ startIdx: -1,
65
+ endIdx: -1,
66
+ };
67
+ }
68
+
69
+ const endIdx = endMarkerIdx + TFX_END.length;
70
+ const content = raw.slice(startIdx, endIdx);
71
+ const versionMatch = content.match(TFX_VERSION_RE);
72
+
73
+ return {
74
+ found: true,
75
+ content,
76
+ version: versionMatch ? versionMatch[1] : null,
77
+ startIdx,
78
+ endIdx,
79
+ };
80
+ }
81
+
82
+ function detectLegacyRange(rawText) {
83
+ const raw = String(rawText || "");
84
+ const matches = LEGACY_PATTERNS.some((pattern) => pattern.test(raw));
85
+ if (!matches) {
86
+ return { found: false, startIdx: -1, endIdx: -1 };
87
+ }
88
+
89
+ const startTag = raw.indexOf("<user_cli_routing>");
90
+ const endTag = raw.indexOf("</user_cli_routing>");
91
+ if (startTag === -1 || endTag === -1 || endTag <= startTag) {
92
+ return { found: true, startIdx: -1, endIdx: -1 };
93
+ }
94
+
95
+ let removeStart = startTag;
96
+ const userOverridesComment = "<!-- USER OVERRIDES";
97
+ const commentIdx = raw.lastIndexOf(userOverridesComment, startTag);
98
+ if (commentIdx !== -1 && startTag - commentIdx < 200) {
99
+ removeStart = commentIdx;
100
+ }
101
+
102
+ return {
103
+ found: true,
104
+ startIdx: removeStart,
105
+ endIdx: endTag + "</user_cli_routing>".length,
106
+ };
107
+ }
108
+
109
+ function normalizeSpacing(text) {
110
+ return String(text || "").replace(/\n{3,}/gu, "\n\n");
111
+ }
112
+
113
+ export function findAllClaudeMdPaths(options = {}) {
114
+ const cwd = options.cwd ? resolve(options.cwd) : process.cwd();
115
+ const homeDir = options.homeDir ? resolve(options.homeDir) : homedir();
116
+ const includeGlobal = options.includeGlobal !== false;
117
+ const includeProject = options.includeProject !== false;
118
+
119
+ const candidates = [];
120
+ if (includeGlobal) candidates.push(join(homeDir, ".claude", "CLAUDE.md"));
121
+ if (includeProject) candidates.push(join(cwd, "CLAUDE.md"));
122
+
123
+ const seen = new Set();
124
+ const paths = [];
125
+ for (const candidate of candidates) {
126
+ const normalized = resolve(candidate);
127
+ if (seen.has(normalized)) continue;
128
+ seen.add(normalized);
129
+ if (existsSync(normalized)) {
130
+ paths.push(normalized);
131
+ }
132
+ }
133
+ return paths;
134
+ }
135
+
136
+ export function writeSection(filePath, options = {}) {
137
+ const absolutePath = resolve(filePath);
138
+ const version = resolveVersion(options.version);
139
+ const template = resolveTemplate(options.template, options.templatePath);
140
+ const block = [
141
+ TFX_START,
142
+ `<!-- TFX:VERSION:${version} -->`,
143
+ template,
144
+ TFX_END,
145
+ ].join("\n");
146
+
147
+ if (!existsSync(absolutePath)) {
148
+ writeFileSync(absolutePath, `${block}\n`, "utf8");
149
+ return { action: "created", version, path: absolutePath };
150
+ }
151
+
152
+ const raw = readFileSync(absolutePath, "utf8");
153
+ const existing = findManagedSection(raw);
154
+
155
+ if (existing.found) {
156
+ const nextContent = `${raw.slice(0, existing.startIdx)}${block}${raw.slice(existing.endIdx)}`;
157
+ writeFileSync(absolutePath, nextContent, "utf8");
158
+ return {
159
+ action: "updated",
160
+ oldVersion: existing.version,
161
+ version,
162
+ path: absolutePath,
163
+ };
164
+ }
165
+
166
+ const omcEndIdx = raw.indexOf(OMC_END);
167
+ if (omcEndIdx !== -1) {
168
+ const insertAt = omcEndIdx + OMC_END.length;
169
+ const before = raw.slice(0, insertAt);
170
+ const after = raw.slice(insertAt);
171
+ const nextContent = `${before}\n${block}${after}`;
172
+ writeFileSync(absolutePath, nextContent, "utf8");
173
+ return { action: "inserted_after_omc", version, path: absolutePath };
174
+ }
175
+
176
+ const separator = raw.endsWith("\n") ? "" : "\n";
177
+ writeFileSync(absolutePath, `${raw}${separator}${block}\n`, "utf8");
178
+ return { action: "appended", version, path: absolutePath };
179
+ }
180
+
181
+ export function migrateClaudeMd(filePath, options = {}) {
182
+ const absolutePath = resolve(filePath);
183
+ if (!existsSync(absolutePath)) {
184
+ return { action: "no_file", removed: [], path: absolutePath };
185
+ }
186
+
187
+ const raw = readFileSync(absolutePath, "utf8");
188
+ const existing = findManagedSection(raw);
189
+ const legacy = detectLegacyRange(raw);
190
+
191
+ if (existing.found && !legacy.found) {
192
+ return {
193
+ action: "already_managed",
194
+ removed: [],
195
+ version: existing.version,
196
+ path: absolutePath,
197
+ };
198
+ }
199
+
200
+ const removed = [];
201
+ let nextContent = raw;
202
+
203
+ if (legacy.found && legacy.startIdx !== -1 && legacy.endIdx !== -1) {
204
+ nextContent = `${nextContent.slice(0, legacy.startIdx)}${nextContent.slice(legacy.endIdx)}`;
205
+ removed.push("<user_cli_routing> block");
206
+ }
207
+
208
+ const existingAfterLegacy = findManagedSection(nextContent);
209
+ if (existingAfterLegacy.found) {
210
+ nextContent = `${nextContent.slice(0, existingAfterLegacy.startIdx)}${nextContent.slice(existingAfterLegacy.endIdx)}`;
211
+ removed.push("old TFX block");
212
+ }
213
+
214
+ writeFileSync(absolutePath, normalizeSpacing(nextContent), "utf8");
215
+ const writeResult = writeSection(absolutePath, options);
216
+
217
+ return {
218
+ action: "migrated",
219
+ removed,
220
+ version: writeResult.version,
221
+ path: absolutePath,
222
+ };
223
+ }
@@ -0,0 +1,62 @@
1
+ // cli-agy.mjs — Antigravity (agy) CLI adapter (Phase 0 PoC).
2
+ //
3
+ // 책임: agy --print 는 stdin-only 이므로 plan() 에서 stdinMode="pipe" 시그널을 반환한다.
4
+ // positional prompt 패턴은 환영 메시지 폴백을 유발하므로 사용 금지.
5
+ //
6
+ // 출처: scripts/tfx-route.sh route_agent() L1052-L1059, run_codex_exec() L2036-L2046.
7
+ // .claude/rules/tfx-update-logic.md Antigravity CLI 1.0.0 정밀 sanity matrix.
8
+
9
+ export const id = "agy";
10
+ export const cliType = "antigravity";
11
+ export const command = "agy";
12
+
13
+ const AGENT_PROFILES = {
14
+ antigravity: {
15
+ profile: "agy_v1",
16
+ timeoutSec: 900,
17
+ runMode: "bg",
18
+ opusOversight: false,
19
+ mcpHint: "docs",
20
+ },
21
+ agy: {
22
+ profile: "agy_v1",
23
+ timeoutSec: 900,
24
+ runMode: "bg",
25
+ opusOversight: false,
26
+ mcpHint: "docs",
27
+ },
28
+ };
29
+
30
+ const DEFAULT_PROFILE = AGENT_PROFILES.antigravity;
31
+
32
+ export function plan({
33
+ agent,
34
+ prompt = "",
35
+ mcpProfile = "auto",
36
+ timeoutSec,
37
+ contextFile,
38
+ } = {}) {
39
+ if (!agent) {
40
+ throw new Error("[cli-agy] agent required");
41
+ }
42
+ const cfg = AGENT_PROFILES[agent] ?? DEFAULT_PROFILE;
43
+ const effectiveTimeoutSec =
44
+ Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec : cfg.timeoutSec;
45
+ return {
46
+ command,
47
+ profile: cfg.profile,
48
+ effort: cfg.profile,
49
+ args: ["--print", "--dangerously-skip-permissions"],
50
+ stdinMode: "pipe",
51
+ timeoutMs: effectiveTimeoutSec * 1000,
52
+ runMode: cfg.runMode,
53
+ opusOversight: cfg.opusOversight,
54
+ mcpProfile: mcpProfile === "auto" ? cfg.mcpHint : mcpProfile,
55
+ promptLength: prompt.length,
56
+ contextFile: contextFile || null,
57
+ };
58
+ }
59
+
60
+ export function describe() {
61
+ return { id, cliType, command, agents: Object.keys(AGENT_PROFILES) };
62
+ }
@@ -0,0 +1,78 @@
1
+ // cli-claude.mjs — Claude native adapter (Phase 0 PoC).
2
+ //
3
+ // 책임: Claude 는 외부 CLI 가 아닌 Claude Code Agent 도구로 dispatch 된다. plan() 은
4
+ // command=null 을 반환하고 호출자가 Agent() 로 라우팅하도록 신호한다.
5
+ //
6
+ // 출처: scripts/tfx-route.sh route_agent() L1062-L1063 (explore|claude),
7
+ // L1196-L1203 (TFX_CLI_MODE=gemini fallback), TFX_VERIFIER_OVERRIDE=claude.
8
+
9
+ export const id = "claude";
10
+ export const cliType = "claude-native";
11
+ export const command = null;
12
+
13
+ const AGENT_PROFILES = {
14
+ explore: {
15
+ timeoutSec: 600,
16
+ runMode: "fg",
17
+ opusOversight: false,
18
+ model: "haiku",
19
+ },
20
+ claude: {
21
+ timeoutSec: 600,
22
+ runMode: "fg",
23
+ opusOversight: false,
24
+ model: "haiku",
25
+ },
26
+ "test-engineer": {
27
+ timeoutSec: 1200,
28
+ runMode: "bg",
29
+ opusOversight: false,
30
+ model: "sonnet",
31
+ },
32
+ "qa-tester": {
33
+ timeoutSec: 1200,
34
+ runMode: "bg",
35
+ opusOversight: false,
36
+ model: "sonnet",
37
+ },
38
+ verifier: {
39
+ timeoutSec: 1200,
40
+ runMode: "fg",
41
+ opusOversight: false,
42
+ model: "sonnet",
43
+ },
44
+ };
45
+
46
+ const DEFAULT_PROFILE = AGENT_PROFILES.explore;
47
+
48
+ export function plan({
49
+ agent,
50
+ prompt = "",
51
+ mcpProfile = "auto",
52
+ timeoutSec,
53
+ contextFile,
54
+ } = {}) {
55
+ if (!agent) {
56
+ throw new Error("[cli-claude] agent required");
57
+ }
58
+ const cfg = AGENT_PROFILES[agent] ?? DEFAULT_PROFILE;
59
+ const effectiveTimeoutSec =
60
+ Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec : cfg.timeoutSec;
61
+ return {
62
+ command,
63
+ profile: "claude-native",
64
+ effort: "n/a",
65
+ timeoutMs: effectiveTimeoutSec * 1000,
66
+ runMode: cfg.runMode,
67
+ opusOversight: cfg.opusOversight,
68
+ model: cfg.model,
69
+ mcpProfile: mcpProfile === "auto" ? null : mcpProfile,
70
+ promptLength: prompt.length,
71
+ contextFile: contextFile || null,
72
+ note: "Claude native — dispatched via Claude Code Agent tool, not CLI",
73
+ };
74
+ }
75
+
76
+ export function describe() {
77
+ return { id, cliType, command, agents: Object.keys(AGENT_PROFILES) };
78
+ }