@triflux/remote 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.
Files changed (162) hide show
  1. package/hub/pipe.mjs +239 -121
  2. package/hub/public/dashboard.html +61 -0
  3. package/hub/server.mjs +2179 -709
  4. package/hub/store-adapter.mjs +59 -26
  5. package/hub/store.mjs +356 -153
  6. package/hub/team/agent-map.json +26 -7
  7. package/hub/team/ansi.mjs +245 -146
  8. package/hub/team/backend.mjs +78 -17
  9. package/hub/team/build-worker-prompt.mjs +73 -0
  10. package/hub/team/check-mcp-hub.mjs +49 -0
  11. package/hub/team/claude-daemon-control.mjs +1130 -0
  12. package/hub/team/claude-native-bridge.mjs +1011 -0
  13. package/hub/team/claude-session-projection.mjs +74 -0
  14. package/hub/team/cli/commands/attach.mjs +57 -37
  15. package/hub/team/cli/commands/control.mjs +29 -8
  16. package/hub/team/cli/commands/debug.mjs +95 -74
  17. package/hub/team/cli/commands/focus.mjs +80 -53
  18. package/hub/team/cli/commands/interrupt.mjs +18 -6
  19. package/hub/team/cli/commands/kill.mjs +16 -5
  20. package/hub/team/cli/commands/list.mjs +31 -24
  21. package/hub/team/cli/commands/send.mjs +19 -6
  22. package/hub/team/cli/commands/start/index.mjs +151 -33
  23. package/hub/team/cli/commands/start/parse-args.mjs +97 -13
  24. package/hub/team/cli/commands/start/start-headless.mjs +155 -37
  25. package/hub/team/cli/commands/start/start-in-process.mjs +50 -40
  26. package/hub/team/cli/commands/start/start-mux.mjs +122 -73
  27. package/hub/team/cli/commands/start/start-wt.mjs +86 -69
  28. package/hub/team/cli/commands/status.mjs +43 -14
  29. package/hub/team/cli/commands/stop.mjs +11 -4
  30. package/hub/team/cli/commands/task.mjs +8 -3
  31. package/hub/team/cli/commands/tasks.mjs +13 -13
  32. package/hub/team/cli/help.mjs +1 -1
  33. package/hub/team/cli/index.mjs +2 -2
  34. package/hub/team/cli/manifest.mjs +38 -8
  35. package/hub/team/cli/render.mjs +52 -30
  36. package/hub/team/cli/services/attach-fallback.mjs +74 -54
  37. package/hub/team/cli/services/hub-client.mjs +44 -16
  38. package/hub/team/cli/services/member-selector.mjs +37 -30
  39. package/hub/team/cli/services/native-control.mjs +144 -116
  40. package/hub/team/cli/services/runtime-mode.mjs +61 -4
  41. package/hub/team/cli/services/state-store.mjs +66 -9
  42. package/hub/team/cli/services/task-model.mjs +40 -30
  43. package/hub/team/codex-review.mjs +521 -0
  44. package/hub/team/conductor-mesh-bridge.mjs +25 -24
  45. package/hub/team/conductor-registry.mjs +121 -0
  46. package/hub/team/conductor.mjs +1379 -671
  47. package/hub/team/consensus-meta.mjs +176 -0
  48. package/hub/team/dashboard-anchor.mjs +4 -5
  49. package/hub/team/dashboard-layout.mjs +3 -1
  50. package/hub/team/dashboard-open.mjs +20 -118
  51. package/hub/team/dashboard.mjs +76 -28
  52. package/hub/team/event-log.mjs +18 -10
  53. package/hub/team/execution-mode.mjs +256 -0
  54. package/hub/team/extract-completion-payload.mjs +110 -0
  55. package/hub/team/git-preflight.mjs +286 -0
  56. package/hub/team/handoff.mjs +49 -18
  57. package/hub/team/headless-bridge-session.mjs +17 -0
  58. package/hub/team/headless.mjs +2213 -1156
  59. package/hub/team/health-probe.mjs +232 -69
  60. package/hub/team/interactive-native-launcher.mjs +106 -0
  61. package/hub/team/interactive-native-worker.mjs +80 -0
  62. package/hub/team/interactive-tui-transport.mjs +465 -0
  63. package/hub/team/launcher-template.mjs +49 -17
  64. package/hub/team/lead-control.mjs +91 -4
  65. package/hub/team/mcp-selector.mjs +145 -0
  66. package/hub/team/native-supervisor.mjs +90 -26
  67. package/hub/team/native.mjs +120 -42
  68. package/hub/team/nativeProxy.mjs +184 -113
  69. package/hub/team/notify.mjs +147 -78
  70. package/hub/team/orchestrator.mjs +173 -161
  71. package/hub/team/pane.mjs +43 -20
  72. package/hub/team/process-cleanup.mjs +69 -25
  73. package/hub/team/psmux.mjs +1022 -281
  74. package/hub/team/recovery-store.mjs +149 -0
  75. package/hub/team/remote-probe.mjs +68 -52
  76. package/hub/team/remote-session.mjs +131 -59
  77. package/hub/team/remote-watcher.mjs +61 -33
  78. package/hub/team/retry-state-machine.mjs +336 -0
  79. package/hub/team/routing.mjs +51 -25
  80. package/hub/team/runtime-strategy.mjs +200 -0
  81. package/hub/team/schemas/worker-completion.json +39 -0
  82. package/hub/team/sentinel-capture.mjs +127 -0
  83. package/hub/team/session-sync.mjs +153 -6
  84. package/hub/team/session.mjs +574 -611
  85. package/hub/team/shared.mjs +13 -13
  86. package/hub/team/staleState.mjs +72 -30
  87. package/hub/team/swarm-cli.mjs +390 -0
  88. package/hub/team/swarm-hypervisor.mjs +1544 -193
  89. package/hub/team/swarm-intent.mjs +216 -0
  90. package/hub/team/swarm-locks.mjs +140 -25
  91. package/hub/team/swarm-planner.mjs +121 -21
  92. package/hub/team/swarm-preflight.mjs +319 -0
  93. package/hub/team/swarm-reconciler.mjs +128 -23
  94. package/hub/team/synapse-cli.mjs +458 -0
  95. package/hub/team/synapse-http.mjs +60 -0
  96. package/hub/team/synapse-registry.mjs +329 -0
  97. package/hub/team/terminal-opener.mjs +216 -0
  98. package/hub/team/tui-core.mjs +292 -0
  99. package/hub/team/tui-lite.mjs +237 -148
  100. package/hub/team/tui-remote-adapter.mjs +18 -34
  101. package/hub/team/tui-synapse.mjs +223 -0
  102. package/hub/team/tui-viewer.mjs +108 -48
  103. package/hub/team/tui-widgets.mjs +262 -0
  104. package/hub/team/tui.mjs +931 -439
  105. package/hub/team/worker-completion-validator.mjs +84 -0
  106. package/hub/team/worker-sandbox.mjs +97 -0
  107. package/hub/team/worker-signal.mjs +263 -0
  108. package/hub/team/worker-signal.types.d.ts +52 -0
  109. package/hub/team/worktree-lifecycle.mjs +517 -63
  110. package/hub/team/wt-manager.mjs +297 -26
  111. package/hub/tools.mjs +477 -252
  112. package/hub/tray.mjs +411 -368
  113. package/hub/workers/antigravity-route-worker.mjs +169 -0
  114. package/hub/workers/claude-worker.mjs +162 -118
  115. package/hub/workers/codex-app-server-worker.mjs +1015 -0
  116. package/hub/workers/codex-mcp.mjs +724 -507
  117. package/hub/workers/delegator-mcp.mjs +570 -499
  118. package/hub/workers/factory.mjs +103 -21
  119. package/hub/workers/gemini-worker.mjs +126 -284
  120. package/hub/workers/interface.mjs +24 -2
  121. package/hub/workers/lib/jsonrpc-stdio.mjs +463 -0
  122. package/hub/workers/worker-utils.mjs +23 -17
  123. package/package.json +4 -3
  124. package/scripts/lib/agent-json.mjs +27 -0
  125. package/scripts/lib/async.mjs +174 -0
  126. package/scripts/lib/claudemd-scanner.mjs +223 -0
  127. package/scripts/lib/cli-agy.mjs +62 -0
  128. package/scripts/lib/cli-claude.mjs +78 -0
  129. package/scripts/lib/cli-codex.mjs +199 -0
  130. package/scripts/lib/cli-gemini.mjs +37 -0
  131. package/scripts/lib/codex-recovery.sh +50 -0
  132. package/scripts/lib/context.mjs +67 -0
  133. package/scripts/lib/cross-review-utils.mjs +59 -0
  134. package/scripts/lib/doctor-env-checks.mjs +121 -0
  135. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  136. package/scripts/lib/env-probe.mjs +269 -0
  137. package/scripts/lib/env.mjs +14 -0
  138. package/scripts/lib/gemini-profiles.mjs +119 -0
  139. package/scripts/lib/handoff.mjs +187 -0
  140. package/scripts/lib/hook-utils.mjs +16 -0
  141. package/scripts/lib/hub.mjs +48 -0
  142. package/scripts/lib/keyword-rules.mjs +195 -0
  143. package/scripts/lib/logger.mjs +105 -0
  144. package/scripts/lib/mcp-filter.mjs +883 -0
  145. package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
  146. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  147. package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
  148. package/scripts/lib/mcp-guard-engine.mjs +2120 -0
  149. package/scripts/lib/mcp-health.mjs +773 -0
  150. package/scripts/lib/mcp-manifest.mjs +89 -0
  151. package/scripts/lib/mcp-server-catalog.mjs +355 -0
  152. package/scripts/lib/pid.mjs +63 -0
  153. package/scripts/lib/process-utils.mjs +26 -0
  154. package/scripts/lib/psmux-info.mjs +224 -0
  155. package/scripts/lib/quota.mjs +47 -0
  156. package/scripts/lib/remote-spawn-transfer.mjs +226 -0
  157. package/scripts/lib/skill-state.mjs +220 -0
  158. package/scripts/lib/skill-template.mjs +375 -0
  159. package/scripts/lib/team.mjs +75 -0
  160. package/scripts/lib/timeout.mjs +66 -0
  161. package/scripts/lib/tmp.mjs +26 -0
  162. package/scripts/lib/toml.mjs +113 -0
@@ -0,0 +1,89 @@
1
+ // scripts/lib/mcp-manifest.mjs
2
+ // MCP 서버 활성화 매니페스트 — 단일 진실 소스.
3
+ // tfx-setup 위저드가 저장하고, gateway/filter가 참조한다.
4
+
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { dirname, join } from "node:path";
8
+
9
+ export const MANIFEST_PATH = join(
10
+ homedir(),
11
+ ".claude",
12
+ "cache",
13
+ "mcp-enabled.json",
14
+ );
15
+
16
+ /** API 키 불필요 — 항상 활성화 for gateway-managed MCP servers */
17
+ export const CORE_SERVERS = Object.freeze(["serena"]);
18
+
19
+ /** 검색 MCP — API 키 필요 */
20
+ export const SEARCH_SERVERS = Object.freeze([
21
+ { name: "brave-search", envVars: ["BRAVE_API_KEY"] },
22
+ { name: "exa", envVars: ["EXA_API_KEY"] },
23
+ { name: "tavily", envVars: ["TAVILY_API_KEY"] },
24
+ ]);
25
+
26
+ /** 통합 MCP — API 키 + 추가 설정 필요 */
27
+ export const INTEGRATION_SERVERS = Object.freeze([
28
+ {
29
+ name: "jira",
30
+ envVars: ["JIRA_API_TOKEN", "JIRA_EMAIL", "JIRA_INSTANCE_URL"],
31
+ },
32
+ { name: "notion", envVars: ["NOTION_TOKEN"] },
33
+ { name: "notion-guest", envVars: ["NOTION_TOKEN"] },
34
+ ]);
35
+
36
+ export function readManifest() {
37
+ if (!existsSync(MANIFEST_PATH)) return null;
38
+ try {
39
+ return JSON.parse(readFileSync(MANIFEST_PATH, "utf8"));
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ export function writeManifest(enabledServers) {
46
+ const dir = dirname(MANIFEST_PATH);
47
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
48
+ const manifest = {
49
+ version: 1,
50
+ updatedAt: new Date().toISOString(),
51
+ enabled: [...new Set([...CORE_SERVERS, ...enabledServers])],
52
+ };
53
+ writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
54
+ return manifest;
55
+ }
56
+
57
+ /**
58
+ * 매니페스트 기준으로 활성 서버만 필터링.
59
+ * @param {Array<string|{name:string}>} allServers — 전체 서버 목록
60
+ * @returns {Array} 활성 서버 목록. 매니페스트 미존재 시 null (레거시 모드).
61
+ */
62
+ export function filterByManifest(allServers) {
63
+ const manifest = readManifest();
64
+ if (!manifest) return null;
65
+ const enabled = new Set(manifest.enabled || []);
66
+ for (const core of CORE_SERVERS) enabled.add(core);
67
+ return allServers.filter((s) =>
68
+ enabled.has(typeof s === "string" ? s : s.name),
69
+ );
70
+ }
71
+
72
+ /**
73
+ * 단일 서버 활성화 여부 확인.
74
+ * 매니페스트 미존재 시 true (레거시 호환).
75
+ */
76
+ export function isServerEnabled(serverName) {
77
+ const manifest = readManifest();
78
+ if (!manifest) return true;
79
+ if (CORE_SERVERS.includes(serverName)) return true;
80
+ return (manifest.enabled || []).includes(serverName);
81
+ }
82
+
83
+ /** 특정 서버에 필요한 환경변수 중 누락된 것 반환 */
84
+ export function getMissingEnvVars(serverName) {
85
+ const all = [...SEARCH_SERVERS, ...INTEGRATION_SERVERS];
86
+ const entry = all.find((s) => s.name === serverName);
87
+ if (!entry) return [];
88
+ return entry.envVars.filter((k) => !process.env[k]);
89
+ }
@@ -0,0 +1,355 @@
1
+ export const SEARCH_SERVER_ORDER = Object.freeze([
2
+ "brave-search",
3
+ "tavily",
4
+ "exa",
5
+ ]);
6
+
7
+ export const MCP_SERVER_TOOL_CATALOG = Object.freeze({
8
+ context7: Object.freeze(["resolve-library-id", "query-docs"]),
9
+ "brave-search": Object.freeze(["brave_web_search", "brave_news_search"]),
10
+ exa: Object.freeze(["web_search_exa", "get_code_context_exa"]),
11
+ tavily: Object.freeze(["tavily_search", "tavily_extract"]),
12
+ playwright: Object.freeze([
13
+ "browser_navigate",
14
+ "browser_navigate_back",
15
+ "browser_snapshot",
16
+ "browser_take_screenshot",
17
+ "browser_wait_for",
18
+ ]),
19
+ "sequential-thinking": Object.freeze(["sequentialthinking"]),
20
+ });
21
+
22
+ export const MCP_SERVER_DOMAIN_TAGS = Object.freeze({
23
+ context7: Object.freeze(["docs", "reference", "api", "sdk", "library"]),
24
+ "brave-search": Object.freeze(["web", "search", "news", "current"]),
25
+ exa: Object.freeze(["code", "repository", "examples", "search"]),
26
+ tavily: Object.freeze([
27
+ "research",
28
+ "search",
29
+ "news",
30
+ "verification",
31
+ "current",
32
+ ]),
33
+ playwright: Object.freeze(["browser", "ui", "visual", "e2e"]),
34
+ "sequential-thinking": Object.freeze([
35
+ "analysis",
36
+ "planning",
37
+ "reasoning",
38
+ "security",
39
+ "review",
40
+ ]),
41
+ });
42
+
43
+ export const DOMAIN_TAG_KEYWORDS = Object.freeze({
44
+ docs: Object.freeze([
45
+ "docs",
46
+ "documentation",
47
+ "manual",
48
+ "guide",
49
+ "문서",
50
+ "가이드",
51
+ "매뉴얼",
52
+ ]),
53
+ reference: Object.freeze([
54
+ "reference",
55
+ "spec",
56
+ "schema",
57
+ "official",
58
+ "레퍼런스",
59
+ "공식",
60
+ "스펙",
61
+ "스키마",
62
+ ]),
63
+ api: Object.freeze([
64
+ "api",
65
+ "endpoint",
66
+ "interface",
67
+ "sdk",
68
+ "호출",
69
+ "엔드포인트",
70
+ ]),
71
+ sdk: Object.freeze([
72
+ "sdk",
73
+ "library",
74
+ "package",
75
+ "framework",
76
+ "라이브러리",
77
+ "패키지",
78
+ "프레임워크",
79
+ ]),
80
+ library: Object.freeze([
81
+ "library",
82
+ "package",
83
+ "framework",
84
+ "module",
85
+ "라이브러리",
86
+ "패키지",
87
+ "모듈",
88
+ ]),
89
+ web: Object.freeze([
90
+ "web",
91
+ "site",
92
+ "article",
93
+ "forum",
94
+ "blog",
95
+ "reddit",
96
+ "웹",
97
+ "사이트",
98
+ "기사",
99
+ "포럼",
100
+ "블로그",
101
+ ]),
102
+ search: Object.freeze([
103
+ "search",
104
+ "browse",
105
+ "lookup",
106
+ "find",
107
+ "검색",
108
+ "조회",
109
+ "탐색",
110
+ "찾아",
111
+ ]),
112
+ news: Object.freeze([
113
+ "latest",
114
+ "recent",
115
+ "news",
116
+ "today",
117
+ "release",
118
+ "announcement",
119
+ "최신",
120
+ "최근",
121
+ "뉴스",
122
+ "오늘",
123
+ "릴리즈",
124
+ "공지",
125
+ ]),
126
+ current: Object.freeze([
127
+ "current",
128
+ "status",
129
+ "pricing",
130
+ "changelog",
131
+ "up-to-date",
132
+ "현재",
133
+ "상태",
134
+ "가격",
135
+ "변경사항",
136
+ ]),
137
+ research: Object.freeze([
138
+ "research",
139
+ "verify",
140
+ "fact-check",
141
+ "investigate",
142
+ "리서치",
143
+ "검증",
144
+ "조사",
145
+ ]),
146
+ verification: Object.freeze([
147
+ "verify",
148
+ "validation",
149
+ "fact-check",
150
+ "audit",
151
+ "검증",
152
+ "확인",
153
+ "감사",
154
+ ]),
155
+ code: Object.freeze([
156
+ "code",
157
+ "repo",
158
+ "repository",
159
+ "source",
160
+ "implementation",
161
+ "bug",
162
+ "fix",
163
+ "test",
164
+ "snippet",
165
+ "cli",
166
+ "코드",
167
+ "리포",
168
+ "저장소",
169
+ "구현",
170
+ "버그",
171
+ "테스트",
172
+ "예제",
173
+ "스크립트",
174
+ ]),
175
+ repository: Object.freeze([
176
+ "repo",
177
+ "repository",
178
+ "source",
179
+ "git",
180
+ "github",
181
+ "리포",
182
+ "저장소",
183
+ "소스",
184
+ ]),
185
+ examples: Object.freeze([
186
+ "example",
187
+ "examples",
188
+ "snippet",
189
+ "sample",
190
+ "예제",
191
+ "샘플",
192
+ ]),
193
+ browser: Object.freeze([
194
+ "browser",
195
+ "page",
196
+ "dom",
197
+ "screenshot",
198
+ "render",
199
+ "브라우저",
200
+ "페이지",
201
+ "스크린샷",
202
+ "렌더",
203
+ ]),
204
+ ui: Object.freeze([
205
+ "ui",
206
+ "ux",
207
+ "layout",
208
+ "responsive",
209
+ "css",
210
+ "html",
211
+ "디자인",
212
+ "레이아웃",
213
+ "반응형",
214
+ ]),
215
+ visual: Object.freeze([
216
+ "visual",
217
+ "screenshot",
218
+ "layout",
219
+ "render",
220
+ "screen",
221
+ "화면",
222
+ "시각",
223
+ "스크린샷",
224
+ ]),
225
+ e2e: Object.freeze([
226
+ "playwright",
227
+ "e2e",
228
+ "click",
229
+ "navigate",
230
+ "automation",
231
+ "playwright",
232
+ "클릭",
233
+ "이동",
234
+ "자동화",
235
+ ]),
236
+ analysis: Object.freeze([
237
+ "analysis",
238
+ "analyze",
239
+ "audit",
240
+ "compare",
241
+ "root cause",
242
+ "분석",
243
+ "검토",
244
+ "비교",
245
+ "원인",
246
+ ]),
247
+ planning: Object.freeze([
248
+ "plan",
249
+ "planning",
250
+ "strategy",
251
+ "design",
252
+ "계획",
253
+ "전략",
254
+ "설계",
255
+ ]),
256
+ reasoning: Object.freeze([
257
+ "reason",
258
+ "reasoning",
259
+ "think",
260
+ "critique",
261
+ "추론",
262
+ "사고",
263
+ "비평",
264
+ ]),
265
+ security: Object.freeze([
266
+ "security",
267
+ "risk",
268
+ "threat",
269
+ "vulnerability",
270
+ "보안",
271
+ "위험",
272
+ "취약점",
273
+ ]),
274
+ review: Object.freeze(["review", "reviewer", "inspect", "리뷰", "검수"]),
275
+ });
276
+
277
+ export const SERVER_EXPLICIT_KEYWORDS = Object.freeze({
278
+ context7: Object.freeze(["context7"]),
279
+ "brave-search": Object.freeze(["brave", "brave-search"]),
280
+ exa: Object.freeze(["exa"]),
281
+ tavily: Object.freeze(["tavily"]),
282
+ playwright: Object.freeze(["playwright"]),
283
+ "sequential-thinking": Object.freeze([
284
+ "sequential-thinking",
285
+ "sequential thinking",
286
+ ]),
287
+ });
288
+
289
+ export function uniqueStrings(values = []) {
290
+ return [
291
+ ...new Set(
292
+ values
293
+ .filter((value) => typeof value === "string" && value.trim())
294
+ .map((value) => value.trim()),
295
+ ),
296
+ ];
297
+ }
298
+
299
+ export function inferDomainTagsFromText(text = "") {
300
+ if (typeof text !== "string" || !text.trim()) return [];
301
+ const normalized = text.toLocaleLowerCase();
302
+ const matched = [];
303
+
304
+ for (const [tag, keywords] of Object.entries(DOMAIN_TAG_KEYWORDS)) {
305
+ if (
306
+ keywords.some((keyword) =>
307
+ normalized.includes(String(keyword).toLocaleLowerCase()),
308
+ )
309
+ ) {
310
+ matched.push(tag);
311
+ }
312
+ }
313
+
314
+ return uniqueStrings(matched);
315
+ }
316
+
317
+ export function getDefaultServerMetadata(serverName = "") {
318
+ const toolCount = MCP_SERVER_TOOL_CATALOG[serverName]?.length || 0;
319
+ const domainTags = uniqueStrings([
320
+ ...(MCP_SERVER_DOMAIN_TAGS[serverName] || []),
321
+ ...inferDomainTagsFromText(serverName),
322
+ ]);
323
+
324
+ return {
325
+ tool_count: toolCount,
326
+ domain_tags: domainTags,
327
+ };
328
+ }
329
+
330
+ export function normalizeServerMetadata(serverName = "", metadata = {}) {
331
+ const fallback = getDefaultServerMetadata(serverName);
332
+ const toolCount = Number.isFinite(metadata.tool_count)
333
+ ? Math.max(0, Math.trunc(metadata.tool_count))
334
+ : fallback.tool_count;
335
+ const domainTags = uniqueStrings([
336
+ ...fallback.domain_tags,
337
+ ...(Array.isArray(metadata.domain_tags) ? metadata.domain_tags : []),
338
+ ...inferDomainTagsFromText(
339
+ [
340
+ serverName,
341
+ typeof metadata.command === "string" ? metadata.command : "",
342
+ typeof metadata.url === "string" ? metadata.url : "",
343
+ ...(Array.isArray(metadata.args) ? metadata.args : []),
344
+ ...(metadata.env && typeof metadata.env === "object"
345
+ ? Object.keys(metadata.env)
346
+ : []),
347
+ ].join(" "),
348
+ ),
349
+ ]);
350
+
351
+ return {
352
+ tool_count: toolCount,
353
+ domain_tags: domainTags,
354
+ };
355
+ }
@@ -0,0 +1,63 @@
1
+ import { appendFileSync, existsSync, readFileSync, rmSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+
4
+ const require = createRequire(import.meta.url);
5
+ let treeKill = null;
6
+ try {
7
+ treeKill = require("tree-kill");
8
+ } catch {
9
+ treeKill = null;
10
+ }
11
+
12
+ function defaultIsAlive(pid) {
13
+ try {
14
+ process.kill(pid, 0);
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ function defaultKillTree(pid) {
22
+ return new Promise((resolve) => {
23
+ if (treeKill) {
24
+ treeKill(pid, "SIGTERM", () => resolve());
25
+ return;
26
+ }
27
+ try {
28
+ process.kill(pid, "SIGTERM");
29
+ } catch {
30
+ // Process already exited.
31
+ }
32
+ resolve();
33
+ });
34
+ }
35
+
36
+ export class PidTracker {
37
+ constructor(path, options = {}) {
38
+ this.path = path;
39
+ this.isAlive = options.isAlive ?? defaultIsAlive;
40
+ this.killTree = options.killTree ?? defaultKillTree;
41
+ }
42
+
43
+ track(pid) {
44
+ if (!pid) return;
45
+ appendFileSync(this.path, `${pid}\n`);
46
+ }
47
+
48
+ list() {
49
+ if (!existsSync(this.path)) return [];
50
+ return readFileSync(this.path, "utf8")
51
+ .split(/\r?\n/)
52
+ .map((line) => Number(line.trim()))
53
+ .filter((pid) => Number.isInteger(pid) && pid > 0);
54
+ }
55
+
56
+ async cleanup() {
57
+ for (const pid of this.list()) {
58
+ if (!this.isAlive(pid)) continue;
59
+ await this.killTree(pid);
60
+ }
61
+ rmSync(this.path, { force: true });
62
+ }
63
+ }
@@ -0,0 +1,26 @@
1
+ // scripts/lib/process-utils.mjs
2
+ // 프로세스 관련 공유 유틸리티 (훅 및 lightweight 스크립트용)
3
+ //
4
+ // Note: hub/lib/process-utils.mjs에는 더 포괄적인 orphan cleanup 로직이 있습니다.
5
+ // 이 파일은 단순 alive 체크만 필요한 훅/스크립트용 (상위 의존성 없이 동작).
6
+
7
+ /**
8
+ * 주어진 PID의 프로세스가 살아있는지 확인합니다.
9
+ * - EPERM: 프로세스는 존재하지만 signal 권한 없음 → alive
10
+ * - ESRCH: 프로세스가 존재하지 않음 → dead
11
+ * - 기타 에러 (invalid pid 포함): dead로 간주
12
+ *
13
+ * @param {number|string} pid
14
+ * @returns {boolean}
15
+ */
16
+ export function isProcessAlive(pid) {
17
+ const n = Number(pid);
18
+ if (!Number.isInteger(n) || n <= 0) return false;
19
+ try {
20
+ process.kill(n, 0);
21
+ return true;
22
+ } catch (e) {
23
+ if (e?.code === "EPERM") return true;
24
+ return false;
25
+ }
26
+ }