@triflux/remote 10.27.0 → 10.28.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.
@@ -1,27 +1,17 @@
1
1
  import crypto from "node:crypto";
2
- import os from "node:os";
3
2
  import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
4
 
5
5
  import {
6
- buildNativeWorkerRosterEntry,
6
+ buildDaemonExecDispatchPayload,
7
7
  deriveClaudeDaemonPaths,
8
- mergeRosterWorkers,
9
- removeRosterWorkers,
10
- } from "./claude-native-bridge.mjs";
11
- import { startInteractiveNativeWorker } from "./interactive-native-worker.mjs";
8
+ dispatchClaudeDaemonJob,
9
+ teardownClaudeDaemonJob,
10
+ } from "./claude-daemon-control.mjs";
11
+ import { encodeBridgeConfig } from "./daemon-pty-tmux-bridge.mjs";
12
12
 
13
- function buildWorkerSocketPaths({ configDir, sessionName, pid, short }) {
14
- const hash = crypto
15
- .createHash("sha256")
16
- .update(`${configDir || ""}:${sessionName}:${pid}:${crypto.randomUUID()}`)
17
- .digest("hex")
18
- .slice(0, 10);
19
- const socketRoot = path.join(os.tmpdir(), `tfx-in-${hash}`);
20
- return {
21
- rvSock: path.join(socketRoot, `${short}.rv.sock`),
22
- ptySock: path.join(socketRoot, `${short}.pty.sock`),
23
- };
24
- }
13
+ const DEFAULT_COLS = 120;
14
+ const DEFAULT_ROWS = 40;
25
15
 
26
16
  export async function launchAdoptedInteractiveWorker({
27
17
  cwd = process.cwd(),
@@ -31,76 +21,99 @@ export async function launchAdoptedInteractiveWorker({
31
21
  role = "interactive",
32
22
  configDir,
33
23
  pid = process.pid,
24
+ env = {},
25
+ cols = DEFAULT_COLS,
26
+ rows = DEFAULT_ROWS,
34
27
  _deps = {},
35
28
  } = {}) {
36
- const startWorker =
37
- _deps.startInteractiveNativeWorker || startInteractiveNativeWorker;
38
- const buildRosterEntry =
39
- _deps.buildNativeWorkerRosterEntry || buildNativeWorkerRosterEntry;
40
- const mergeWorkers = _deps.mergeRosterWorkers || mergeRosterWorkers;
41
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;
42
35
 
43
36
  const resolvedCwd = path.resolve(cwd);
44
- const effectiveSessionName =
45
- sessionName || `tfx-${cli}-${role}-${process.pid}`;
37
+ const effectiveSessionName = sessionName || `tfx-${cli}-${role}-${pid}`;
46
38
  const paths = derivePaths({ configDir });
47
- const short = crypto.randomUUID().replaceAll("-", "").slice(0, 8);
48
- const { rvSock, ptySock } = buildWorkerSocketPaths({
49
- 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({
50
45
  sessionName: effectiveSessionName,
51
- pid,
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({
52
56
  short,
57
+ cwd: resolvedCwd,
58
+ command,
59
+ name: displayName,
60
+ cols,
61
+ rows,
53
62
  });
54
- const displayName = `Triflux ${cli} ${role}`;
55
-
56
- let worker;
57
- let closed = false;
58
- let closePromise = null;
59
-
63
+ let dispatched;
60
64
  try {
61
- worker = await startWorker({
62
- short,
63
- rvSock,
64
- ptySock,
65
- sessionName: effectiveSessionName,
66
- cwd: resolvedCwd,
67
- launchCmd,
68
- pid,
69
- });
70
- const workerShort = worker.short || short;
71
- const workerPid = worker.pid || pid;
72
- const entry = buildRosterEntry({
73
- short: workerShort,
74
- pid: workerPid,
75
- cwd: resolvedCwd,
76
- rendezvousSock: worker.rvSock,
77
- ptySock: worker.ptySock,
65
+ dispatched = await dispatchJob({
66
+ paths,
67
+ controlSock: paths.controlSock,
68
+ payload,
69
+ agent: cli,
78
70
  name: displayName,
79
- prompt: displayName,
71
+ cwd: resolvedCwd,
72
+ dispatchTimeoutMs: 5000,
80
73
  });
81
- await mergeWorkers(paths.rosterPath, { [workerShort]: entry });
82
-
83
- return {
84
- short: workerShort,
85
- displayName,
86
- rosterPath: paths.rosterPath,
87
- close() {
88
- if (!closePromise) {
89
- closePromise = (async () => {
90
- if (closed) return;
91
- closed = true;
92
- try {
93
- await worker.close();
94
- } finally {
95
- await removeRosterWorkers(paths.rosterPath, [workerShort]);
96
- }
97
- })();
98
- }
99
- return closePromise;
100
- },
101
- };
102
74
  } catch (error) {
103
- if (worker) await worker.close().catch(() => {});
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(() => {});
104
84
  throw error;
105
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("'", "'\\''")}'`;
106
119
  }
@@ -42,10 +42,10 @@ const ESCALATION_CHAIN_CONFIG_PATH = ".triflux/config/escalation-chain.json";
42
42
 
43
43
  // Escalation chain (2026-05-27 정책):
44
44
  // 1. codex gpt-5.5 — 코드/추론/비용에서 mini/5.3-codex 중간 단계를 대체
45
- // 2. claude opus-4-7 — 최종 수단
45
+ // 2. claude opus-4-8 — 최종 수단
46
46
  const DEFAULT_ESCALATION_CHAIN = Object.freeze([
47
47
  Object.freeze({ cli: "codex", model: "gpt-5.5" }),
48
- Object.freeze({ cli: "claude", model: "opus-4-7" }),
48
+ Object.freeze({ cli: "claude", model: "opus-4-8" }),
49
49
  ]);
50
50
 
51
51
  const STUCK_THRESHOLD = 3;
@@ -160,8 +160,11 @@ export const KIND_MAP = Object.freeze({
160
160
  configWarning: "error",
161
161
  });
162
162
 
163
+ // NOTE: codex 0.135.0 rejects `--skip-git-repo-check` as a global option (exit 2:
164
+ // "unexpected argument"); it lives at `codex exec` subcommand scope, and the
165
+ // `app-server` subcommand does not need it. Empirically verified against
166
+ // codex-cli 0.135.0. Do not re-add it as a global flag here.
163
167
  export const DEFAULT_CODEX_APP_SERVER_ARGS = Object.freeze([
164
- "--skip-git-repo-check",
165
168
  "app-server",
166
169
  "--listen",
167
170
  "stdio://",