@triflux/core 10.1.0 → 10.26.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/hooks/agent-route-guard.mjs +14 -10
- package/hooks/cross-review-tracker.mjs +21 -8
- package/hooks/error-context.mjs +20 -8
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +267 -35
- package/hooks/hook-registry.json +343 -256
- package/hooks/hooks.json +49 -1
- package/hooks/keyword-rules.json +191 -31
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hooks/mcp-config-watcher.mjs +6 -2
- package/hooks/permission-safe-allow.mjs +233 -0
- package/hooks/pipeline-stop.mjs +369 -22
- package/hooks/post-tool-tips.mjs +306 -0
- package/hooks/pre-compact-snapshot.mjs +181 -0
- package/hooks/safety-guard.mjs +262 -34
- package/hooks/session-end-cleanup.mjs +345 -0
- package/hooks/session-start-fast.mjs +214 -0
- package/hooks/subagent-tracker.mjs +267 -0
- package/hooks/subagent-verifier.mjs +9 -4
- package/hub/account-broker.mjs +1036 -66
- package/hub/adaptive-diagnostic.mjs +75 -48
- package/hub/adaptive-inject.mjs +96 -58
- package/hub/adaptive-memory.mjs +156 -42
- package/hub/adaptive.mjs +60 -31
- package/hub/assign-callbacks.mjs +163 -126
- package/hub/bridge.mjs +1051 -290
- package/hub/cli-adapter-base.mjs +321 -97
- package/hub/codex-adapter.mjs +179 -93
- package/hub/codex-compat.mjs +4 -4
- package/hub/codex-preflight.mjs +113 -37
- package/hub/delegator/contracts.mjs +22 -21
- package/hub/delegator/index.mjs +14 -14
- package/hub/delegator/schema/delegator-tools.schema.json +1 -1
- package/hub/delegator/service.mjs +93 -64
- package/hub/delegator/tool-definitions.mjs +35 -35
- package/hub/dynamic-routing-engine.mjs +511 -0
- package/hub/fullcycle.mjs +34 -18
- package/hub/gemini-adapter.mjs +82 -108
- package/hub/hitl.mjs +202 -143
- package/hub/intent.mjs +167 -38
- package/hub/lib/bash-path.mjs +73 -0
- package/hub/lib/cache-guard.mjs +43 -17
- package/hub/lib/env-detect.mjs +145 -0
- package/hub/lib/hosts-compat.mjs +319 -0
- package/hub/lib/mcp-response-cache.mjs +239 -0
- package/hub/lib/memory-store.mjs +285 -111
- package/hub/lib/path-utils.mjs +165 -0
- package/hub/lib/phase-manager.mjs +493 -0
- package/hub/lib/process-utils.mjs +797 -104
- package/hub/lib/prompt-tmp.mjs +33 -0
- package/hub/lib/risk-tier.mjs +53 -0
- package/hub/lib/spawn-trace.mjs +561 -0
- package/hub/lib/ssh-command.mjs +44 -24
- package/hub/lib/ssh-retry.mjs +5 -1
- package/hub/lib/staged-file-detect.mjs +43 -0
- package/hub/lib/state-snapshot.mjs +293 -0
- package/hub/lib/tfx-route-args.mjs +309 -0
- package/hub/lib/timeline-adapter.mjs +190 -0
- package/hub/lib/trace-recorder.mjs +153 -0
- package/hub/lib/uuidv7.mjs +4 -3
- package/hub/memory-doctor.mjs +266 -106
- package/hub/middleware/quota-middleware.mjs +70 -0
- package/hub/middleware/request-logger.mjs +61 -34
- package/hub/paths.mjs +9 -9
- package/hub/pipeline/gates/confidence.mjs +34 -15
- package/hub/pipeline/gates/consensus.mjs +27 -15
- package/hub/pipeline/gates/index.mjs +7 -3
- package/hub/pipeline/gates/selfcheck.mjs +57 -19
- package/hub/pipeline/index.mjs +77 -42
- package/hub/pipeline/state.mjs +10 -10
- package/hub/pipeline/transitions.mjs +40 -23
- package/hub/platform.mjs +65 -48
- package/hub/quality/deslop.mjs +70 -49
- package/hub/reflexion.mjs +87 -13
- package/hub/research.mjs +33 -25
- package/hub/router.mjs +941 -791
- package/hub/routing/complexity.mjs +132 -29
- package/hub/routing/index.mjs +17 -12
- package/hub/routing/q-learning.mjs +78 -30
- package/hub/routing-snapshot.mjs +261 -0
- package/hub/session-fingerprint.mjs +127 -61
- package/hub/state.mjs +86 -44
- package/hub/team/claude-daemon-control.mjs +1130 -0
- package/hub/team/retry-state-machine.mjs +336 -0
- package/hub/token-mode.mjs +72 -71
- package/hub/workers/worker-utils.mjs +23 -17
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +190 -27
- package/hud/context-monitor.mjs +133 -44
- package/hud/hud-qos-status.mjs +157 -50
- package/hud/mission-board.mjs +56 -0
- package/hud/providers/claude.mjs +398 -114
- package/hud/providers/codex.mjs +96 -21
- package/hud/providers/gemini.mjs +210 -52
- package/hud/renderers.mjs +440 -127
- package/hud/terminal.mjs +101 -31
- package/hud/utils.mjs +81 -39
- package/mesh/index.mjs +69 -0
- package/mesh/mesh-budget.mjs +137 -0
- package/mesh/mesh-heartbeat.mjs +100 -0
- package/mesh/mesh-protocol.mjs +96 -0
- package/mesh/mesh-queue.mjs +163 -0
- package/mesh/mesh-registry.mjs +78 -0
- package/mesh/mesh-router.mjs +77 -0
- package/package.json +5 -1
- package/scripts/lib/agent-json.mjs +27 -0
- package/scripts/lib/async.mjs +174 -0
- package/scripts/lib/claudemd-scanner.mjs +6 -1
- package/scripts/lib/cli-agy.mjs +62 -0
- package/scripts/lib/cli-claude.mjs +78 -0
- package/scripts/lib/cli-codex.mjs +199 -0
- package/scripts/lib/cli-gemini.mjs +37 -0
- package/scripts/lib/codex-recovery.sh +50 -0
- package/scripts/lib/context.mjs +3 -3
- package/scripts/lib/cross-review-utils.mjs +13 -5
- package/scripts/lib/doctor-env-checks.mjs +121 -0
- package/scripts/lib/dynamic-route-cli.mjs +107 -0
- package/scripts/lib/env-probe.mjs +58 -30
- package/scripts/lib/env.mjs +14 -0
- package/scripts/lib/gemini-profiles.mjs +44 -10
- package/scripts/lib/handoff.mjs +33 -17
- package/scripts/lib/hook-utils.mjs +8 -6
- package/scripts/lib/hub.mjs +48 -0
- package/scripts/lib/keyword-rules.mjs +49 -20
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +391 -247
- package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
- package/scripts/lib/mcp-gateway-servers.mjs +70 -0
- package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
- package/scripts/lib/mcp-guard-engine.mjs +1281 -115
- package/scripts/lib/mcp-health.mjs +773 -0
- package/scripts/lib/mcp-manifest.mjs +24 -14
- package/scripts/lib/mcp-server-catalog.mjs +355 -118
- package/scripts/lib/pid.mjs +63 -0
- package/scripts/lib/process-utils.mjs +26 -0
- package/scripts/lib/psmux-info.mjs +117 -12
- package/scripts/lib/quota.mjs +47 -0
- package/scripts/lib/remote-spawn-transfer.mjs +44 -14
- package/scripts/lib/skill-state.mjs +220 -0
- package/scripts/lib/skill-template.mjs +32 -7
- package/scripts/lib/team.mjs +75 -0
- package/scripts/lib/timeout.mjs +66 -0
- package/scripts/lib/tmp.mjs +26 -0
- package/scripts/lib/toml.mjs +113 -0
package/hub/state.mjs
CHANGED
|
@@ -1,20 +1,34 @@
|
|
|
1
|
-
import { execSync } from
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import {
|
|
3
|
+
closeSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
openSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
renameSync,
|
|
9
|
+
statSync,
|
|
10
|
+
unlinkSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
const PROJECT_ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
18
|
+
const PID_FILE_NAME = "hub.pid";
|
|
19
|
+
const LEGACY_STATE_FILE_NAME = "hub-state.json";
|
|
20
|
+
const LOCK_FILE_NAME = "hub-start.lock";
|
|
11
21
|
|
|
12
22
|
let heldLockPath = null;
|
|
13
23
|
let heldLockFd = null;
|
|
14
24
|
let cachedVersionHash = null;
|
|
15
25
|
|
|
16
26
|
function getStateDir(options = {}) {
|
|
17
|
-
return
|
|
27
|
+
return (
|
|
28
|
+
options.stateDir ||
|
|
29
|
+
process.env.TFX_HUB_STATE_DIR?.trim() ||
|
|
30
|
+
join(homedir(), ".claude", "cache", "tfx-hub")
|
|
31
|
+
);
|
|
18
32
|
}
|
|
19
33
|
|
|
20
34
|
function getStatePath(options = {}) {
|
|
@@ -55,25 +69,32 @@ function safeReplaceFile(tempPath, targetPath) {
|
|
|
55
69
|
try {
|
|
56
70
|
renameSync(tempPath, targetPath);
|
|
57
71
|
} catch (error) {
|
|
58
|
-
if (![
|
|
59
|
-
try {
|
|
72
|
+
if (!["EEXIST", "EPERM", "EACCES"].includes(error?.code)) {
|
|
73
|
+
try {
|
|
74
|
+
unlinkSync(tempPath);
|
|
75
|
+
} catch {}
|
|
60
76
|
throw error;
|
|
61
77
|
}
|
|
62
|
-
try {
|
|
78
|
+
try {
|
|
79
|
+
unlinkSync(targetPath);
|
|
80
|
+
} catch {}
|
|
63
81
|
renameSync(tempPath, targetPath);
|
|
64
82
|
}
|
|
65
83
|
}
|
|
66
84
|
|
|
67
85
|
function writeJsonFile(targetPath, payload) {
|
|
68
86
|
const tempPath = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
|
|
69
|
-
writeFileSync(tempPath, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
87
|
+
writeFileSync(tempPath, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
88
|
+
encoding: "utf8",
|
|
89
|
+
mode: 0o600,
|
|
90
|
+
});
|
|
70
91
|
safeReplaceFile(tempPath, targetPath);
|
|
71
92
|
}
|
|
72
93
|
|
|
73
94
|
function readJsonFile(filePath) {
|
|
74
95
|
try {
|
|
75
96
|
if (!existsSync(filePath)) return null;
|
|
76
|
-
return parseJson(readFileSync(filePath,
|
|
97
|
+
return parseJson(readFileSync(filePath, "utf8"), null);
|
|
77
98
|
} catch {
|
|
78
99
|
return null;
|
|
79
100
|
}
|
|
@@ -82,7 +103,7 @@ function readJsonFile(filePath) {
|
|
|
82
103
|
/**
|
|
83
104
|
* 허브의 현재 상태(PID, 포트, 버전 등)를 파일에 기록합니다.
|
|
84
105
|
* 원자적(atomic) 쓰기를 위해 임시 파일을 생성한 후 교체하는 방식을 사용합니다.
|
|
85
|
-
*
|
|
106
|
+
*
|
|
86
107
|
* @param {object} payload - 상태 데이터
|
|
87
108
|
* @param {number} payload.pid - 허브 프로세스 ID
|
|
88
109
|
* @param {number} payload.port - 허브 서버 포트
|
|
@@ -93,31 +114,39 @@ function readJsonFile(filePath) {
|
|
|
93
114
|
* @param {string} [options.stateDir] - 상태 파일이 저장될 디렉토리
|
|
94
115
|
* @returns {object} 기록된 상태 데이터
|
|
95
116
|
*/
|
|
96
|
-
export function writeState(
|
|
117
|
+
export function writeState(
|
|
118
|
+
{ pid, port, version, sessionId, startedAt, ...rest },
|
|
119
|
+
options = {},
|
|
120
|
+
) {
|
|
97
121
|
const stateDir = getStateDir(options);
|
|
98
122
|
const statePath = getStatePath(options);
|
|
99
123
|
const payload = { pid, port, version, sessionId, startedAt, ...rest };
|
|
100
124
|
|
|
101
125
|
mkdirSync(stateDir, { recursive: true });
|
|
102
126
|
writeJsonFile(statePath, payload);
|
|
103
|
-
try {
|
|
127
|
+
try {
|
|
128
|
+
unlinkSync(getLegacyStatePath(options));
|
|
129
|
+
} catch {}
|
|
104
130
|
return payload;
|
|
105
131
|
}
|
|
106
132
|
|
|
107
133
|
/**
|
|
108
134
|
* 파일로부터 허브의 현재 상태를 읽어옵니다.
|
|
109
|
-
*
|
|
135
|
+
*
|
|
110
136
|
* @param {object} [options] - 옵션
|
|
111
137
|
* @param {string} [options.stateDir] - 상태 파일이 저장된 디렉토리
|
|
112
138
|
* @returns {object|null} 읽어온 상태 데이터 또는 실패 시 null
|
|
113
139
|
*/
|
|
114
140
|
export function readState(options = {}) {
|
|
115
|
-
return
|
|
141
|
+
return (
|
|
142
|
+
readJsonFile(getStatePath(options)) ??
|
|
143
|
+
readJsonFile(getLegacyStatePath(options))
|
|
144
|
+
);
|
|
116
145
|
}
|
|
117
146
|
|
|
118
147
|
/**
|
|
119
148
|
* 지정된 포트에서 실행 중인 허브 서버의 헬스 체크를 수행합니다.
|
|
120
|
-
*
|
|
149
|
+
*
|
|
121
150
|
* @param {number|string} port - 서버 포트
|
|
122
151
|
* @param {object} [options] - 옵션
|
|
123
152
|
* @param {number} [options.timeoutMs=1000] - 요청 타임아웃
|
|
@@ -133,7 +162,7 @@ export async function isServerHealthy(port, options = {}) {
|
|
|
133
162
|
|
|
134
163
|
try {
|
|
135
164
|
const response = await fetch(`${baseUrl}/health`, {
|
|
136
|
-
method:
|
|
165
|
+
method: "GET",
|
|
137
166
|
signal: AbortSignal.timeout(timeoutMs),
|
|
138
167
|
});
|
|
139
168
|
if (!response.ok) return false;
|
|
@@ -147,7 +176,7 @@ export async function isServerHealthy(port, options = {}) {
|
|
|
147
176
|
/**
|
|
148
177
|
* 현재 프로젝트의 버전 해시를 생성합니다.
|
|
149
178
|
* package.json의 버전과 Git commit SHA를 조합합니다.
|
|
150
|
-
*
|
|
179
|
+
*
|
|
151
180
|
* @param {object} [options] - 옵션
|
|
152
181
|
* @param {boolean} [options.force=false] - 캐시를 무시하고 새로 생성할지 여부
|
|
153
182
|
* @returns {string} 버전 해시 문자열
|
|
@@ -155,21 +184,21 @@ export async function isServerHealthy(port, options = {}) {
|
|
|
155
184
|
export function getVersionHash(options = {}) {
|
|
156
185
|
if (cachedVersionHash && !options.force) return cachedVersionHash;
|
|
157
186
|
|
|
158
|
-
const packageJsonPath = join(PROJECT_ROOT,
|
|
159
|
-
const pkg = parseJson(readFileSync(packageJsonPath,
|
|
160
|
-
const version = String(pkg?.version ||
|
|
187
|
+
const packageJsonPath = join(PROJECT_ROOT, "package.json");
|
|
188
|
+
const pkg = parseJson(readFileSync(packageJsonPath, "utf8"), {});
|
|
189
|
+
const version = String(pkg?.version || "0.0.0").trim();
|
|
161
190
|
|
|
162
|
-
let sha = String(process.env.TFX_HUB_GIT_SHA ||
|
|
191
|
+
let sha = String(process.env.TFX_HUB_GIT_SHA || "").trim();
|
|
163
192
|
if (!sha) {
|
|
164
193
|
try {
|
|
165
|
-
sha = execSync(
|
|
194
|
+
sha = execSync("git rev-parse --short HEAD", {
|
|
166
195
|
cwd: PROJECT_ROOT,
|
|
167
|
-
encoding:
|
|
168
|
-
stdio: [
|
|
196
|
+
encoding: "utf8",
|
|
197
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
169
198
|
windowsHide: true,
|
|
170
199
|
}).trim();
|
|
171
200
|
} catch {
|
|
172
|
-
sha =
|
|
201
|
+
sha = "";
|
|
173
202
|
}
|
|
174
203
|
}
|
|
175
204
|
|
|
@@ -180,7 +209,7 @@ export function getVersionHash(options = {}) {
|
|
|
180
209
|
/**
|
|
181
210
|
* 허브 시작 시 중복 실행을 방지하기 위한 잠금(lock)을 획득합니다.
|
|
182
211
|
* 이미 실행 중인 다른 프로세스가 있는지 확인하고 유효한 잠금을 획득할 때까지 재시도합니다.
|
|
183
|
-
*
|
|
212
|
+
*
|
|
184
213
|
* @param {object} [options] - 옵션
|
|
185
214
|
* @param {number} [options.timeoutMs=3000] - 최대 대기 시간
|
|
186
215
|
* @param {number} [options.pollMs=50] - 재시도 간격
|
|
@@ -202,27 +231,38 @@ export async function acquireLock(options = {}) {
|
|
|
202
231
|
|
|
203
232
|
while (Date.now() <= deadline) {
|
|
204
233
|
try {
|
|
205
|
-
const fd = openSync(lockPath,
|
|
206
|
-
writeFileSync(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
234
|
+
const fd = openSync(lockPath, "wx", 0o600);
|
|
235
|
+
writeFileSync(
|
|
236
|
+
fd,
|
|
237
|
+
`${JSON.stringify(
|
|
238
|
+
{
|
|
239
|
+
pid: process.pid,
|
|
240
|
+
createdAt: new Date().toISOString(),
|
|
241
|
+
},
|
|
242
|
+
null,
|
|
243
|
+
2,
|
|
244
|
+
)}\n`,
|
|
245
|
+
"utf8",
|
|
246
|
+
);
|
|
210
247
|
heldLockFd = fd;
|
|
211
248
|
heldLockPath = lockPath;
|
|
212
249
|
return { path: lockPath };
|
|
213
250
|
} catch (error) {
|
|
214
|
-
if (error?.code !==
|
|
251
|
+
if (error?.code !== "EEXIST") {
|
|
215
252
|
throw error;
|
|
216
253
|
}
|
|
217
254
|
|
|
218
255
|
try {
|
|
219
|
-
const raw = readFileSync(lockPath,
|
|
256
|
+
const raw = readFileSync(lockPath, "utf8");
|
|
220
257
|
const data = parseJson(raw, {});
|
|
221
258
|
const stats = statSync(lockPath);
|
|
259
|
+
const STALE_LOCK_AGE_MS = 60_000;
|
|
222
260
|
const staleByPid = !isPidAlive(data?.pid);
|
|
223
|
-
const staleByAge = Date.now() - stats.mtimeMs >
|
|
261
|
+
const staleByAge = Date.now() - stats.mtimeMs > STALE_LOCK_AGE_MS;
|
|
224
262
|
if (staleByPid || staleByAge) {
|
|
225
|
-
try {
|
|
263
|
+
try {
|
|
264
|
+
unlinkSync(lockPath);
|
|
265
|
+
} catch {}
|
|
226
266
|
continue;
|
|
227
267
|
}
|
|
228
268
|
} catch {}
|
|
@@ -236,7 +276,7 @@ export async function acquireLock(options = {}) {
|
|
|
236
276
|
|
|
237
277
|
/**
|
|
238
278
|
* 획득했던 잠금을 해제합니다. 잠금 파일을 삭제하고 관련 리소스를 정리합니다.
|
|
239
|
-
*
|
|
279
|
+
*
|
|
240
280
|
* @param {object} [options] - 옵션
|
|
241
281
|
* @param {string} [options.lockPath] - 명시적인 잠금 파일 경로
|
|
242
282
|
*/
|
|
@@ -244,7 +284,9 @@ export function releaseLock(options = {}) {
|
|
|
244
284
|
const lockPath = options.lockPath || heldLockPath || getLockPath(options);
|
|
245
285
|
|
|
246
286
|
if (heldLockFd !== null) {
|
|
247
|
-
try {
|
|
287
|
+
try {
|
|
288
|
+
closeSync(heldLockFd);
|
|
289
|
+
} catch {}
|
|
248
290
|
heldLockFd = null;
|
|
249
291
|
}
|
|
250
292
|
|