@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.
- package/hub/team/claude-daemon-control.mjs +214 -0
- package/hub/team/claude-native-bridge.mjs +45 -73
- package/hub/team/daemon-pty-tmux-bridge.mjs +146 -0
- package/hub/team/headless.mjs +23 -42
- package/hub/team/interactive-native-launcher.mjs +88 -75
- package/hub/team/retry-state-machine.mjs +2 -2
- package/hub/workers/codex-app-server-worker.mjs +4 -1
- package/hub/workers/lib/jsonrpc-ws-uds.mjs +686 -0
- package/package.json +1 -1
- package/scripts/lib/mcp-gateway-health-check.mjs +65 -0
- package/scripts/lib/mcp-guard-engine.mjs +52 -8
|
@@ -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
|
-
|
|
6
|
+
buildDaemonExecDispatchPayload,
|
|
7
7
|
deriveClaudeDaemonPaths,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from "./claude-
|
|
11
|
-
import {
|
|
8
|
+
dispatchClaudeDaemonJob,
|
|
9
|
+
teardownClaudeDaemonJob,
|
|
10
|
+
} from "./claude-daemon-control.mjs";
|
|
11
|
+
import { encodeBridgeConfig } from "./daemon-pty-tmux-bridge.mjs";
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
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.
|
|
48
|
-
const
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
let worker;
|
|
57
|
-
let closed = false;
|
|
58
|
-
let closePromise = null;
|
|
59
|
-
|
|
63
|
+
let dispatched;
|
|
60
64
|
try {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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://",
|