@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,435 +0,0 @@
1
- // hub/team/health-probe.mjs — 4단계 health model + INPUT_WAIT 감지
2
- // 기존 cli-adapter-base.mjs:stallThresholdMs(30s)와 headless.mjs:STALL_DEFAULTS(120s)를
3
- // 4단계 probe 모델로 교체. stdout+stderr 통합 스트림으로 평가 (F3 해결).
4
-
5
- import { mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
6
- import { tmpdir } from "node:os";
7
- import { dirname, join } from "node:path";
8
-
9
- /**
10
- * Health probe level 정의.
11
- * L0: Process alive (PID 존재 + exit code 없음)
12
- * L1: Output advancing (stdout+stderr 통합, 30s)
13
- * L1.5: INPUT_WAIT 감지 (질문 패턴이면 stall이 아니라 input-wait)
14
- * L2: MCP connected (opt-in, heartbeat, 30s)
15
- * L3: Prompt acknowledged (첫 tool call/텍스트, 120s)
16
- */
17
- export const PROBE_LEVELS = Object.freeze({
18
- L0: "alive",
19
- L1: "advancing",
20
- L2: "mcp_connected",
21
- L3: "prompt_ack",
22
- });
23
-
24
- /** 기본 설정 (기존 stallThresholdMs/stallTimeout 값 계승) */
25
- export const PROBE_DEFAULTS = Object.freeze({
26
- intervalMs: 5_000,
27
- probeTimeoutMs: 5_000,
28
- l1ThresholdMs: 30_000,
29
- l2ThresholdMs: 30_000,
30
- l3ThresholdMs: 120_000,
31
- // #168 P3: default off → on. checkMcp 미주입 시에도 probeL2 가 skip 반환하므로
32
- // safe. conductor wiring 이 checkMcp 를 주입하면 실제 L2 판정이 활성화된다.
33
- // opt-out: TFX_PROBE_L2=0 (conductor 에서 false 주입).
34
- enableL2: true,
35
- writeStateFile: false,
36
- stateDir: join(tmpdir(), "tfx-probe"),
37
- });
38
-
39
- /**
40
- * stdin 입력 대기 패턴 (Codex 질문 블로킹 감지)
41
- * Codex가 질문하며 stdin을 기다리는 경우 stall이 아니라 INPUT_WAIT로 분류.
42
- */
43
- const INPUT_WAIT_PATTERNS = [
44
- /\?\s*$/m, // 물음표로 끝나는 줄
45
- /\b(y\/n|yes\/no)\b/i, // y/n 프롬프트
46
- /\b(choose|select|pick)\b.*:/i, // choose/select 프롬프트
47
- /\b(confirm|approve|proceed)\b/i, // confirm 프롬프트
48
- /\b(enter|input|type)\b.*:/i, // 입력 요청
49
- /\[.*\]:\s*$/m, // [default]: 형태
50
- />\s*$/m, // > 프롬프트
51
- ];
52
-
53
- /**
54
- * 최근 output에서 INPUT_WAIT 패턴 감지.
55
- * @param {string} recentOutput — 최근 stdout+stderr 통합 텍스트
56
- * @returns {{ detected: boolean, pattern: string|null }}
57
- */
58
- export function detectInputWait(recentOutput) {
59
- if (!recentOutput) return { detected: false, pattern: null };
60
- // 마지막 5줄만 검사 (전체 output이 아닌 최근 출력)
61
- const lines = recentOutput
62
- .split(/\r?\n/)
63
- .filter(Boolean)
64
- .slice(-5)
65
- .join("\n");
66
- for (const re of INPUT_WAIT_PATTERNS) {
67
- if (re.test(lines)) {
68
- return { detected: true, pattern: re.source };
69
- }
70
- }
71
- return { detected: false, pattern: null };
72
- }
73
-
74
- /**
75
- * Health probe 팩토리.
76
- * @param {object} session — probe 대상 세션 상태 객체
77
- * @param {number|null} session.pid — 프로세스 PID
78
- * @param {function} session.getRecentOutput — () => string (최근 stdout+stderr)
79
- * @param {function} session.getOutputBytes — () => number (총 output 바이트)
80
- * @param {boolean} [session.alive] — 프로세스 alive 여부 (외부 업데이트)
81
- * @param {object} [opts] — PROBE_DEFAULTS 오버라이드
82
- * @param {function} [opts.onProbe] — (level, result) => void 콜백
83
- * @param {function} [opts.checkMcp] — () => Promise<boolean> MCP heartbeat 체커 (L2용)
84
- * @returns {{ start, stop, probe, getStatus }}
85
- */
86
- export function createHealthProbe(session, opts = {}) {
87
- const config = { ...PROBE_DEFAULTS, ...opts };
88
- let timer = null;
89
- let started = false;
90
- // stopped flag는 in-flight probe()가 stop() 사이의 await 점에서
91
- // writeState/unlink 와 race 하는 것을 막는다. start() 시 false 로 reset.
92
- let stopped = false;
93
- // P0 (#167 review): start() 마다 증가. probe() 가 시작 시 캡처 → writeState() 가 epoch
94
- // 비교로 stop()→start() 사이의 in-flight 가 새 run 의 state 를 덮는 race 차단.
95
- // stopped flag 만으로는 start() 가 stopped=false 로 reset 한 직후 race 못 막음.
96
- let runEpoch = 0;
97
- // P1-1 (#167 review): Set 으로 모든 in-flight probe 추적. interval 이 빨라 N+1 이 N 끝나기
98
- // 전에 시작되면 단일 var 는 N 을 덮어써 stopAndDrain() 시 N 이 누락된다.
99
- const inFlightProbes = new Set();
100
-
101
- // L1 tracking
102
- let lastOutputBytes = 0;
103
- let lastOutputChangeAt = Date.now();
104
-
105
- // L3 tracking
106
- let promptAcked = false;
107
- let spawnedAt = Date.now();
108
-
109
- const status = {
110
- l0: null, // 'ok' | 'fail'
111
- l1: null, // 'ok' | 'stall' | 'input_wait'
112
- l2: null, // 'ok' | 'fail' | 'skip'
113
- l3: null, // 'ok' | 'timeout'
114
- lastProbeAt: null,
115
- inputWaitPattern: null,
116
- };
117
-
118
- function getStateFilePath() {
119
- if (typeof config.stateFile === "string" && config.stateFile.length > 0) {
120
- return config.stateFile;
121
- }
122
- const pid = session.pid;
123
- if (pid == null || pid <= 0) return null;
124
- return join(config.stateDir, `${pid}.json`);
125
- }
126
-
127
- function deriveState(result) {
128
- if (result.l0 === "fail") return "exited";
129
- if (result.l1 === "input_wait") return "input_wait";
130
- if (result.l2 === "fail") return "mcp_initializing";
131
- if (result.l1 === "stall") return "stalled";
132
- if (result.l3 === "timeout") return "reasoning";
133
- return "active";
134
- }
135
-
136
- function writeState(result, probeEpoch) {
137
- // stop() 직후 in-flight probe()의 재생성 방지.
138
- // P0 (#167): probeEpoch !== runEpoch 면 이전 run 의 stale probe 가 새 run 의 state 를
139
- // 덮으려는 시도 → skip.
140
- if (stopped) return;
141
- if (probeEpoch !== undefined && probeEpoch !== runEpoch) return;
142
- if (!config.writeStateFile && !config.stateFile) return;
143
- const stateFile = getStateFilePath();
144
- if (!stateFile) return;
145
- const payload =
146
- JSON.stringify(
147
- {
148
- pid: session.pid ?? null,
149
- state: deriveState(result),
150
- result,
151
- updatedAt: new Date(result.ts).toISOString(),
152
- },
153
- null,
154
- 2,
155
- ) + "\n";
156
- // tmp+rename 으로 atomic write — heartbeat 의 sed 가 부분 파일을 읽는 race 제거.
157
- // tmp 는 같은 디렉토리에 둬야 EXDEV (cross-device link) 가 안 난다.
158
- const tmpPath = `${stateFile}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
159
- try {
160
- mkdirSync(dirname(stateFile), { recursive: true });
161
- writeFileSync(tmpPath, payload, "utf8");
162
- try {
163
- renameSync(tmpPath, stateFile);
164
- } catch (renameErr) {
165
- // Windows: 대상이 존재할 때 EPERM/EACCES.
166
- // P1-2 (#167 review): backup-then-swap 으로 기존 파일 보존. 옛 unlinkSync→renameSync
167
- // 패턴은 1차 unlink 후 2차 rename 실패 시 기존 파일과 tmp 둘 다 잃었다.
168
- if (
169
- renameErr?.code === "EEXIST" ||
170
- renameErr?.code === "EPERM" ||
171
- renameErr?.code === "EACCES"
172
- ) {
173
- const backupPath = `${stateFile}.old-${process.pid}-${Date.now()}`;
174
- let backupCreated = false;
175
- try {
176
- renameSync(stateFile, backupPath);
177
- backupCreated = true;
178
- } catch {
179
- // backup 실패 (대상 없음/잠김) — 그래도 진행 (tmp→stateFile 시도)
180
- }
181
- try {
182
- renameSync(tmpPath, stateFile);
183
- if (backupCreated) {
184
- try {
185
- unlinkSync(backupPath);
186
- } catch {}
187
- }
188
- } catch (secondErr) {
189
- // 2차도 실패 → backup 복구해서 기존 파일 보존
190
- if (backupCreated) {
191
- try {
192
- renameSync(backupPath, stateFile);
193
- } catch {}
194
- }
195
- throw secondErr;
196
- }
197
- } else {
198
- throw renameErr;
199
- }
200
- }
201
- } catch {
202
- // probe state is advisory only — tmp cleanup
203
- try {
204
- unlinkSync(tmpPath);
205
- } catch {}
206
- }
207
- }
208
-
209
- /**
210
- * L0: Process alive check.
211
- */
212
- function probeL0() {
213
- const alive =
214
- session.alive !== undefined
215
- ? session.alive
216
- : session.pid != null && session.pid > 0;
217
- status.l0 = alive ? "ok" : "fail";
218
- return status.l0;
219
- }
220
-
221
- /**
222
- * L1 + L1.5: Output advancing + INPUT_WAIT 감지.
223
- */
224
- function probeL1() {
225
- const currentBytes =
226
- typeof session.getOutputBytes === "function"
227
- ? session.getOutputBytes()
228
- : 0;
229
-
230
- const now = Date.now();
231
-
232
- if (currentBytes !== lastOutputBytes) {
233
- lastOutputBytes = currentBytes;
234
- lastOutputChangeAt = now;
235
- status.l1 = "ok";
236
- status.inputWaitPattern = null;
237
- return "ok";
238
- }
239
-
240
- const silenceMs = now - lastOutputChangeAt;
241
-
242
- if (silenceMs >= config.l1ThresholdMs) {
243
- // L1.5: INPUT_WAIT 감지 — stall 전에 질문 패턴 체크
244
- const recentOutput =
245
- typeof session.getRecentOutput === "function"
246
- ? session.getRecentOutput()
247
- : "";
248
- const inputWait = detectInputWait(recentOutput);
249
-
250
- if (inputWait.detected) {
251
- status.l1 = "input_wait";
252
- status.inputWaitPattern = inputWait.pattern;
253
- return "input_wait";
254
- }
255
-
256
- status.l1 = "stall";
257
- status.inputWaitPattern = null;
258
- return "stall";
259
- }
260
-
261
- // 아직 threshold 미달
262
- status.l1 = "ok";
263
- return "ok";
264
- }
265
-
266
- /**
267
- * L2: MCP connected (opt-in).
268
- */
269
- async function probeL2() {
270
- if (!config.enableL2) {
271
- status.l2 = "skip";
272
- return "skip";
273
- }
274
- if (typeof config.checkMcp !== "function") {
275
- status.l2 = "skip";
276
- return "skip";
277
- }
278
- try {
279
- const connected = await Promise.race([
280
- config.checkMcp(),
281
- new Promise((_, reject) =>
282
- setTimeout(
283
- () => reject(new Error("probe_timeout")),
284
- config.probeTimeoutMs,
285
- ),
286
- ),
287
- ]);
288
- status.l2 = connected ? "ok" : "fail";
289
- } catch {
290
- status.l2 = "fail";
291
- }
292
- return status.l2;
293
- }
294
-
295
- /**
296
- * L3: Prompt acknowledged.
297
- * 첫 번째 meaningful output이 나오면 ack.
298
- */
299
- function probeL3() {
300
- if (promptAcked) {
301
- status.l3 = "ok";
302
- return "ok";
303
- }
304
-
305
- const currentBytes =
306
- typeof session.getOutputBytes === "function"
307
- ? session.getOutputBytes()
308
- : 0;
309
-
310
- if (currentBytes > 0) {
311
- promptAcked = true;
312
- status.l3 = "ok";
313
- return "ok";
314
- }
315
-
316
- const elapsed = Date.now() - spawnedAt;
317
- if (elapsed >= config.l3ThresholdMs) {
318
- status.l3 = "timeout";
319
- return "timeout";
320
- }
321
-
322
- status.l3 = null; // 아직 판정 전
323
- return null;
324
- }
325
-
326
- /**
327
- * 전체 probe 실행 (L0→L1→L2→L3).
328
- * @returns {Promise<object|null>} probe 결과. stop() 이후 호출이면 null.
329
- */
330
- async function probe() {
331
- if (stopped) return null;
332
- // P0 (#167): probe 시작 시 epoch 캡처. start() 가 새 epoch 로 바꾸면 이 probe 의
333
- // writeState 는 stale 로 판정 → skip.
334
- const probeEpoch = runEpoch;
335
- const promise = (async () => {
336
- const result = {
337
- l0: probeL0(),
338
- l1: probeL1(),
339
- l2: await probeL2(),
340
- l3: probeL3(),
341
- inputWaitPattern: status.inputWaitPattern,
342
- ts: Date.now(),
343
- };
344
- status.lastProbeAt = result.ts;
345
- writeState(result, probeEpoch);
346
-
347
- if (typeof config.onProbe === "function") {
348
- config.onProbe(result);
349
- }
350
-
351
- return result;
352
- })();
353
- // P1-1 (#167): Set 으로 모든 in-flight 추적. 단일 var 패턴은 N+1 이 N 끝나기 전에
354
- // 시작되면 N 을 덮어써 stopAndDrain() 시 N 이 누락된다.
355
- inFlightProbes.add(promise);
356
- promise.finally(() => inFlightProbes.delete(promise));
357
- return promise;
358
- }
359
-
360
- function start() {
361
- if (started) return;
362
- started = true;
363
- stopped = false;
364
- // P0 (#167): epoch 증가 — 이전 run 의 in-flight probe 가 새 run 의 state 를 덮지 못하게.
365
- runEpoch += 1;
366
- spawnedAt = Date.now();
367
- lastOutputChangeAt = Date.now();
368
- lastOutputBytes = 0;
369
- promptAcked = false;
370
-
371
- timer = setInterval(() => {
372
- void probe();
373
- }, config.intervalMs);
374
- timer.unref?.();
375
-
376
- // 즉시 첫 probe 실행
377
- void probe();
378
- }
379
-
380
- function stop() {
381
- if (!started) return;
382
- started = false;
383
- // stopped 를 먼저 set 해야 in-flight probe()의 writeState() 가 skip 된다.
384
- // 이후 unlink — in-flight 가 끝나도 writeState 가 no-op 이므로 재생성 없음.
385
- stopped = true;
386
- if (timer) {
387
- clearInterval(timer);
388
- timer = null;
389
- }
390
- if (config.writeStateFile || config.stateFile) {
391
- try {
392
- const stateFile = getStateFilePath();
393
- if (stateFile) unlinkSync(stateFile);
394
- } catch {}
395
- }
396
- }
397
-
398
- /**
399
- * stop() 후 in-flight probe() 가 완료될 때까지 대기.
400
- * 결정적 종료가 필요한 테스트/teardown 용. conductor 의 sync stop() 호출자는
401
- * 그대로 stop() 만 호출하면 stopped flag 가 race 를 막는다.
402
- */
403
- async function stopAndDrain() {
404
- stop();
405
- // P1-1 (#167): 모든 in-flight probe 대기. allSettled 로 unhandled rejection 방지.
406
- if (inFlightProbes.size > 0) {
407
- await Promise.allSettled(Array.from(inFlightProbes));
408
- }
409
- }
410
-
411
- /** L1 tracking 리셋 (restart 후 호출) */
412
- function resetTracking() {
413
- lastOutputBytes = 0;
414
- lastOutputChangeAt = Date.now();
415
- promptAcked = false;
416
- spawnedAt = Date.now();
417
- status.l0 = null;
418
- status.l1 = null;
419
- status.l2 = null;
420
- status.l3 = null;
421
- status.inputWaitPattern = null;
422
- }
423
-
424
- return Object.freeze({
425
- start,
426
- stop,
427
- stopAndDrain,
428
- probe,
429
- resetTracking,
430
- getStatus: () => ({ ...status }),
431
- get started() {
432
- return started;
433
- },
434
- });
435
- }
@@ -1,119 +0,0 @@
1
- import crypto from "node:crypto";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- import {
6
- buildDaemonExecDispatchPayload,
7
- deriveClaudeDaemonPaths,
8
- dispatchClaudeDaemonJob,
9
- teardownClaudeDaemonJob,
10
- } from "./claude-daemon-control.mjs";
11
- import { encodeBridgeConfig } from "./daemon-pty-tmux-bridge.mjs";
12
-
13
- const DEFAULT_COLS = 120;
14
- const DEFAULT_ROWS = 40;
15
-
16
- export async function launchAdoptedInteractiveWorker({
17
- cwd = process.cwd(),
18
- sessionName,
19
- launchCmd = "codex",
20
- cli = "codex",
21
- role = "interactive",
22
- configDir,
23
- pid = process.pid,
24
- env = {},
25
- cols = DEFAULT_COLS,
26
- rows = DEFAULT_ROWS,
27
- _deps = {},
28
- } = {}) {
29
- const derivePaths = _deps.deriveClaudeDaemonPaths || deriveClaudeDaemonPaths;
30
- const encodeConfig = _deps.encodeBridgeConfig || encodeBridgeConfig;
31
- const buildPayload =
32
- _deps.buildDaemonExecDispatchPayload || buildDaemonExecDispatchPayload;
33
- const dispatchJob = _deps.dispatchClaudeDaemonJob || dispatchClaudeDaemonJob;
34
- const teardownJob = _deps.teardownClaudeDaemonJob || teardownClaudeDaemonJob;
35
-
36
- const resolvedCwd = path.resolve(cwd);
37
- const effectiveSessionName = sessionName || `tfx-${cli}-${role}-${pid}`;
38
- const paths = derivePaths({ configDir });
39
- const short = crypto.randomBytes(4).toString("hex");
40
- const displayName = `Triflux ${cli} ${role}`;
41
- const bridgePath = fileURLToPath(
42
- new URL("./daemon-pty-tmux-bridge.mjs", import.meta.url),
43
- );
44
- const bridgeConfig = encodeConfig({
45
- sessionName: effectiveSessionName,
46
- launchCmd,
47
- cwd: resolvedCwd,
48
- env,
49
- cols,
50
- rows,
51
- });
52
- const command = `${shellQuote(process.execPath)} ${shellQuote(
53
- bridgePath,
54
- )} --config ${bridgeConfig}`;
55
- const payload = buildPayload({
56
- short,
57
- cwd: resolvedCwd,
58
- command,
59
- name: displayName,
60
- cols,
61
- rows,
62
- });
63
- let dispatched;
64
- try {
65
- dispatched = await dispatchJob({
66
- paths,
67
- controlSock: paths.controlSock,
68
- payload,
69
- agent: cli,
70
- name: displayName,
71
- cwd: resolvedCwd,
72
- dispatchTimeoutMs: 5000,
73
- });
74
- } catch (error) {
75
- // dispatch may have started a daemon job (pid assigned) before a later
76
- // step (bridge-session resolve / projection write) threw. Tear down by
77
- // short + sessionId so a failed launch never leaks a running pty/tmux job.
78
- await teardownJob({
79
- controlSock: paths.controlSock,
80
- paths,
81
- short,
82
- sessionId: payload.sessionId,
83
- }).catch(() => {});
84
- throw error;
85
- }
86
-
87
- let closed = false;
88
- let closePromise = null;
89
-
90
- return {
91
- short,
92
- displayName,
93
- sessionId: dispatched.sessionId,
94
- pid: dispatched.pid,
95
- bridgeSessionId: dispatched.bridgeSessionId,
96
- sessionProjectionPath: dispatched.sessionProjectionPath,
97
- controlSock: paths.controlSock,
98
- close() {
99
- if (!closePromise) {
100
- closePromise = (async () => {
101
- if (closed) return;
102
- closed = true;
103
- await teardownJob({
104
- controlSock: paths.controlSock,
105
- paths,
106
- short,
107
- sessionProjectionPath: dispatched.sessionProjectionPath,
108
- sessionId: dispatched.sessionId,
109
- });
110
- })();
111
- }
112
- return closePromise;
113
- },
114
- };
115
- }
116
-
117
- function shellQuote(value) {
118
- return `'${String(value).replaceAll("'", "'\\''")}'`;
119
- }
@@ -1,80 +0,0 @@
1
- import { startNativeWorkerFacade } from "./claude-native-bridge.mjs";
2
- import { createInteractiveTuiTransport } from "./interactive-tui-transport.mjs";
3
-
4
- // Output currently flows through capture-pane snapshots, so attach clients may
5
- // receive whole-screen refreshes rather than byte-accurate terminal deltas.
6
- export async function startInteractiveNativeWorker({
7
- short,
8
- rvSock,
9
- ptySock,
10
- sessionName,
11
- cwd,
12
- launchCmd,
13
- pid = process.pid,
14
- env = {},
15
- createTransport = createInteractiveTuiTransport,
16
- } = {}) {
17
- let facade = null;
18
- let closePromise = null;
19
-
20
- const transport = createTransport({
21
- sessionName,
22
- cwd,
23
- launchCmd,
24
- env,
25
- onData(chunk) {
26
- if (facade) facade.writeOutput(chunk);
27
- },
28
- });
29
-
30
- async function close({ exitCode = 0, waitForPtyEnd = false } = {}) {
31
- if (!closePromise) {
32
- closePromise = (async () => {
33
- await transport.stop().catch(() => {});
34
- if (waitForPtyEnd) {
35
- await new Promise((resolve) => setTimeout(resolve, 10));
36
- }
37
- if (facade) await facade.close({ exitCode });
38
- })();
39
- }
40
- return closePromise;
41
- }
42
-
43
- try {
44
- facade = await startNativeWorkerFacade({
45
- short,
46
- rvSock,
47
- ptySock,
48
- pid,
49
- workerType: "interactive",
50
- onInput(payload) {
51
- void Promise.resolve()
52
- .then(() => transport.writeInput(payload))
53
- .catch(() => {
54
- void close({ exitCode: 1 }).catch(() => {});
55
- });
56
- },
57
- onKill() {
58
- void close({ waitForPtyEnd: true }).catch(() => {});
59
- },
60
- });
61
- } catch (error) {
62
- await transport.stop().catch(() => {});
63
- throw error;
64
- }
65
-
66
- try {
67
- await transport.start();
68
- } catch (error) {
69
- await close({ exitCode: 1 }).catch(() => {});
70
- throw error;
71
- }
72
-
73
- return {
74
- short,
75
- rvSock,
76
- ptySock,
77
- pid,
78
- close,
79
- };
80
- }