triflux 10.17.1 → 10.17.3

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.
@@ -9,7 +9,7 @@
9
9
  {
10
10
  "name": "triflux",
11
11
  "description": "Tri-CLI orchestrator for Claude Code. Routes tasks across Claude + Codex + Gemini with consensus intelligence, natural language routing, 42 skills, and cross-model review.",
12
- "version": "10.17.1",
12
+ "version": "10.17.3",
13
13
  "author": {
14
14
  "name": "tellang"
15
15
  },
@@ -30,5 +30,5 @@
30
30
  ]
31
31
  }
32
32
  ],
33
- "version": "10.17.1"
33
+ "version": "10.17.3"
34
34
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "10.17.1",
3
+ "version": "10.17.3",
4
4
  "description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
5
5
  "author": {
6
6
  "name": "tellang"
@@ -233,6 +233,29 @@ function hasLiveAncestorChain(pid, procMap, protectedPids) {
233
233
  return false;
234
234
  }
235
235
 
236
+ function hasLiveCliDescendant(pid, procMap) {
237
+ const children = new Map();
238
+ for (const proc of procMap.values()) {
239
+ if (!Number.isFinite(proc.ppid) || proc.ppid <= 0) continue;
240
+ const list = children.get(proc.ppid) || [];
241
+ list.push(proc);
242
+ children.set(proc.ppid, list);
243
+ }
244
+
245
+ const visited = new Set();
246
+ const stack = [...(children.get(pid) || [])];
247
+ while (stack.length > 0) {
248
+ const proc = stack.pop();
249
+ if (!proc || visited.has(proc.pid)) continue;
250
+ visited.add(proc.pid);
251
+ if (LIVE_CLI_SESSION_ROOT_NAMES.has(normalizeName(proc.name))) {
252
+ return true;
253
+ }
254
+ stack.push(...(children.get(proc.pid) || []));
255
+ }
256
+ return false;
257
+ }
258
+
236
259
  /**
237
260
  * Legacy wrapper for scoped orphan node runtime cleanup.
238
261
  * @param {Parameters<typeof cleanupOrphanRuntimeProcesses>[0]} opts
@@ -808,7 +831,8 @@ export function cleanupOrphanRuntimeProcesses({
808
831
  if (legacy) {
809
832
  if (
810
833
  LEGACY_ORPHAN_KILLABLE_NAMES.has(name) &&
811
- !hasLiveAncestorChain(proc.pid, procMap, protectedSet)
834
+ !hasLiveAncestorChain(proc.pid, procMap, protectedSet) &&
835
+ !hasLiveCliDescendant(proc.pid, procMap)
812
836
  ) {
813
837
  shouldKill = true;
814
838
  killReason = "legacy_orphan_ancestor_chain_dead";
@@ -816,7 +840,8 @@ export function cleanupOrphanRuntimeProcesses({
816
840
  } else if (name === "bun.exe") {
817
841
  if (
818
842
  hasExactGbrainServe(proc.commandLine) &&
819
- !hasLiveAncestorChain(proc.pid, procMap, protectedSet)
843
+ !hasLiveAncestorChain(proc.pid, procMap, protectedSet) &&
844
+ !hasLiveCliDescendant(proc.pid, procMap)
820
845
  ) {
821
846
  shouldKill = true;
822
847
  killReason = "bun_gbrain_serve_orphan";
package/hub/server.mjs CHANGED
@@ -962,6 +962,38 @@ export async function startHub({
962
962
  const tools = createTools(store, router, hitl, pipe);
963
963
  const transports = new Map();
964
964
 
965
+ async function closeMcpTransportSession(sid, session, reason) {
966
+ if (!sid) return;
967
+ if (!session) {
968
+ transports.delete(sid);
969
+ return;
970
+ }
971
+ if (session.closing) {
972
+ transports.delete(sid);
973
+ return;
974
+ }
975
+
976
+ session.closing = true;
977
+ transports.delete(sid);
978
+
979
+ try {
980
+ await session.mcp.close();
981
+ } catch (error) {
982
+ hubLog.debug(
983
+ { sid, reason, err: String(error?.message || error) },
984
+ "hub.mcp_session_close_mcp_failed",
985
+ );
986
+ }
987
+ try {
988
+ await session.transport.close();
989
+ } catch (error) {
990
+ hubLog.debug(
991
+ { sid, reason, err: String(error?.message || error) },
992
+ "hub.mcp_session_close_transport_failed",
993
+ );
994
+ }
995
+ }
996
+
965
997
  function createMcpForSession() {
966
998
  const mcp = new Server(
967
999
  { name: "tfx-hub", version: "1.0.0" },
@@ -1705,19 +1737,17 @@ export async function startHub({
1705
1737
  sessionIdGenerator: () => randomUUID(),
1706
1738
  onsessioninitialized: (sid) => {
1707
1739
  transport._lastActivity = Date.now();
1708
- transports.set(sid, { transport, mcp });
1740
+ transports.set(sid, { transport, mcp, closing: false });
1709
1741
  },
1710
1742
  });
1711
1743
  transport.onclose = () => {
1712
- if (transport.sessionId) {
1713
- const session = transports.get(transport.sessionId);
1714
- if (session) {
1715
- try {
1716
- session.mcp.close();
1717
- } catch {}
1718
- }
1719
- transports.delete(transport.sessionId);
1720
- }
1744
+ const sid = transport.sessionId;
1745
+ if (sid)
1746
+ void closeMcpTransportSession(
1747
+ sid,
1748
+ transports.get(sid),
1749
+ "transport.onclose",
1750
+ );
1721
1751
  };
1722
1752
  const mcp = createMcpForSession();
1723
1753
  await mcp.connect(transport);
@@ -1808,13 +1838,7 @@ export async function startHub({
1808
1838
  for (const [sid, session] of transports) {
1809
1839
  if (now - (session.transport._lastActivity || 0) <= SESSION_TTL_MS)
1810
1840
  continue;
1811
- try {
1812
- session.mcp.close();
1813
- } catch {}
1814
- try {
1815
- session.transport.close();
1816
- } catch {}
1817
- transports.delete(sid);
1841
+ void closeMcpTransportSession(sid, session, "session_ttl");
1818
1842
  }
1819
1843
  }, 60000);
1820
1844
  sessionTimer.unref();
@@ -2058,13 +2082,8 @@ export async function startHub({
2058
2082
  if (idleTimer) {
2059
2083
  clearInterval(idleTimer);
2060
2084
  }
2061
- for (const [, session] of transports) {
2062
- try {
2063
- await session.mcp.close();
2064
- } catch {}
2065
- try {
2066
- await session.transport.close();
2067
- } catch {}
2085
+ for (const [sid, session] of Array.from(transports)) {
2086
+ await closeMcpTransportSession(sid, session, "hub.stop");
2068
2087
  }
2069
2088
  transports.clear();
2070
2089
  await pipe.stop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "10.17.1",
3
+ "version": "10.17.3",
4
4
  "description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
5
5
  "type": "module",
6
6
  "bin": {
@@ -90,6 +90,27 @@ function hasProtectedAncestor(pid, procMap) {
90
90
  return false;
91
91
  }
92
92
 
93
+ function hasProtectedDescendant(pid, procMap) {
94
+ const children = new Map();
95
+ for (const proc of procMap.values()) {
96
+ if (!Number.isFinite(proc.ppid) || proc.ppid <= 0) continue;
97
+ const list = children.get(proc.ppid) || [];
98
+ list.push(proc);
99
+ children.set(proc.ppid, list);
100
+ }
101
+
102
+ const seen = new Set();
103
+ const stack = [...(children.get(Number(pid)) || [])];
104
+ while (stack.length > 0) {
105
+ const proc = stack.pop();
106
+ if (!proc || seen.has(proc.pid)) continue;
107
+ seen.add(proc.pid);
108
+ if (PROTECTED_ANCESTOR_NAMES.has(proc.name)) return true;
109
+ stack.push(...(children.get(proc.pid) || []));
110
+ }
111
+ return false;
112
+ }
113
+
93
114
  export function shouldKillTrackedPid({
94
115
  pid,
95
116
  pidFileMtimeMs,
@@ -110,6 +131,7 @@ export function shouldKillTrackedPid({
110
131
  }
111
132
 
112
133
  if (hasProtectedAncestor(pid, procMap)) return false;
134
+ if (hasProtectedDescendant(pid, procMap)) return false;
113
135
 
114
136
  return true;
115
137
  }