@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.
- package/cto/brief.mjs +0 -12
- package/cto/collect.mjs +0 -73
- package/cto/current.schema.json +0 -4
- package/cto/status.mjs +2 -50
- package/hub/index.mjs +0 -8
- package/hub/team/agent-map.json +0 -1
- package/hub/team/ansi.mjs +1 -35
- package/hub/team/backend.mjs +9 -145
- package/hub/team/cli/commands/attach.mjs +1 -17
- package/hub/team/cli/commands/control.mjs +12 -38
- package/hub/team/cli/commands/debug.mjs +1 -37
- package/hub/team/cli/commands/focus.mjs +5 -31
- package/hub/team/cli/commands/interrupt.mjs +6 -25
- package/hub/team/cli/commands/kill.mjs +1 -30
- package/hub/team/cli/commands/list.mjs +0 -16
- package/hub/team/cli/commands/send.mjs +6 -26
- package/hub/team/cli/commands/start/index.mjs +41 -133
- package/hub/team/cli/commands/start/parse-args.mjs +15 -40
- package/hub/team/cli/commands/start/start-headless.mjs +138 -105
- package/hub/team/cli/commands/start/start-mux.mjs +0 -3
- package/hub/team/cli/commands/status.mjs +2 -57
- package/hub/team/cli/commands/stop.mjs +14 -19
- package/hub/team/cli/help.mjs +4 -5
- package/hub/team/cli/manifest.mjs +5 -1
- package/hub/team/cli/render.mjs +0 -12
- package/hub/team/cli/services/hub-client.mjs +2 -88
- package/hub/team/cli/services/runtime-mode.mjs +27 -98
- package/hub/team/handoff.mjs +3 -34
- package/hub/team/headless.mjs +13 -754
- package/hub/team/orchestrator.mjs +4 -30
- package/hub/team/pane.mjs +6 -28
- package/hub/team/psmux.mjs +0 -1
- package/hub/team/remote-session.mjs +1 -2
- package/hub/team/session.mjs +7 -91
- package/hub/team/swarm-intent.mjs +0 -198
- package/hub/team/tui-core.mjs +1 -1
- package/hub/team/tui-viewer.mjs +2 -2
- package/hub/team/tui.mjs +8 -269
- package/hub/team/wt-manager.mjs +2 -2
- package/hub/workers/factory.mjs +4 -37
- package/hub/workers/lib/jsonrpc-ws-uds.mjs +0 -2
- package/package.json +1 -1
- package/scripts/lib/agent-route-policy.mjs +162 -14
- package/scripts/lib/claudemd-scanner.mjs +2 -199
- package/scripts/lib/cli-claude.mjs +0 -2
- package/scripts/lib/context.mjs +3 -19
- package/scripts/lib/env-probe.mjs +5 -64
- package/scripts/lib/gemini-profiles.mjs +2 -69
- package/scripts/lib/machine-profile.mjs +3 -17
- package/scripts/lib/mcp-filter.mjs +109 -53
- package/scripts/lib/mcp-health.mjs +2 -2
- package/scripts/lib/mcp-server-catalog.mjs +2 -2
- package/scripts/lib/psmux-info.mjs +11 -59
- package/scripts/lib/skill-template.mjs +0 -245
- package/hub/team/build-worker-prompt.mjs +0 -92
- package/hub/team/check-mcp-hub.mjs +0 -49
- package/hub/team/claude-native-bridge.mjs +0 -987
- package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
- package/hub/team/cli/commands/start/start-wt.mjs +0 -86
- package/hub/team/cli/services/native-control.mjs +0 -145
- package/hub/team/codex-review.mjs +0 -520
- package/hub/team/conductor-mesh-bridge.mjs +0 -122
- package/hub/team/conductor.mjs +0 -1529
- package/hub/team/consensus-meta.mjs +0 -176
- package/hub/team/cto-pull.mjs +0 -31
- package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
- package/hub/team/dashboard.mjs +0 -322
- package/hub/team/event-log.mjs +0 -84
- package/hub/team/execution-mode.mjs +0 -324
- package/hub/team/extract-completion-payload.mjs +0 -110
- package/hub/team/headless-bridge-session.mjs +0 -17
- package/hub/team/health-probe.mjs +0 -435
- package/hub/team/interactive-native-launcher.mjs +0 -119
- package/hub/team/interactive-native-worker.mjs +0 -80
- package/hub/team/interactive-tui-transport.mjs +0 -479
- package/hub/team/launcher-template.mjs +0 -144
- package/hub/team/mcp-selector.mjs +0 -145
- package/hub/team/native-supervisor.mjs +0 -460
- package/hub/team/native.mjs +0 -727
- package/hub/team/recovery-store.mjs +0 -149
- package/hub/team/remote-probe.mjs +0 -292
- package/hub/team/remote-watcher.mjs +0 -506
- package/hub/team/runtime-strategy.mjs +0 -200
- package/hub/team/schemas/worker-completion.json +0 -39
- package/hub/team/sentinel-capture.mjs +0 -127
- package/hub/team/swarm-cli.mjs +0 -390
- package/hub/team/swarm-hypervisor.mjs +0 -2206
- package/hub/team/swarm-locks.mjs +0 -335
- package/hub/team/swarm-planner.mjs +0 -371
- package/hub/team/swarm-preflight.mjs +0 -366
- package/hub/team/swarm-reconciler.mjs +0 -242
- package/hub/team/tui-remote-adapter.mjs +0 -377
- package/hub/team/worker-completion-validator.mjs +0 -79
- package/hub/team/worktree-lifecycle.mjs +0 -639
- package/hub/team/wt-templates.json +0 -43
- package/hub/workers/antigravity-route-worker.mjs +0 -179
- package/scripts/lib/dynamic-route-cli.mjs +0 -107
|
@@ -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
|
-
}
|