@elyun/bylane 1.25.0 → 1.27.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/package.json +1 -1
- package/src/cli.js +30 -16
- package/src/loop-utils.js +36 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -223,7 +223,7 @@ if (command === 'install') {
|
|
|
223
223
|
}
|
|
224
224
|
} else if (command === 'loop') {
|
|
225
225
|
const subCmd = args[1] || 'start'
|
|
226
|
-
const { resolveLoopMode, startTmuxLoops, stopTmuxLoops, isTmuxSessionAlive } = await import('./loop-utils.js')
|
|
226
|
+
const { resolveLoopMode, startTmuxLoops, stopTmuxLoops, isTmuxSessionAlive, findLoopPid } = await import('./loop-utils.js')
|
|
227
227
|
const { loadConfig } = await import('./config.js')
|
|
228
228
|
const config = loadConfig()
|
|
229
229
|
const sessionName = config.loop?.sessionName ?? 'bylane-loops'
|
|
@@ -264,20 +264,31 @@ if (command === 'install') {
|
|
|
264
264
|
stopped = true
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
// 2)
|
|
267
|
+
// 2) PID 기반 종료 (state → pgrep fallback)
|
|
268
268
|
const { readState, writeState } = await import('./state.js')
|
|
269
269
|
for (const loopName of ['review-loop', 'respond-loop']) {
|
|
270
|
-
const
|
|
271
|
-
if (!
|
|
272
|
-
|
|
270
|
+
const found = findLoopPid(loopName)
|
|
271
|
+
if (!found) {
|
|
272
|
+
// state가 running이면 정리
|
|
273
|
+
const state = readState(loopName)
|
|
274
|
+
if (state?.status === 'running') {
|
|
275
|
+
writeState(loopName, { ...state, status: 'stopped', stoppedAt: new Date().toISOString() })
|
|
276
|
+
console.log(` ${loopName}: 프로세스를 찾을 수 없음 — 상태 정리`)
|
|
277
|
+
stopped = true
|
|
278
|
+
}
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const { pid, source } = found
|
|
273
283
|
try {
|
|
274
|
-
process.kill(pid, 0) // PID 살아있는지 확인
|
|
275
284
|
process.kill(pid, 'SIGTERM')
|
|
276
|
-
|
|
277
|
-
|
|
285
|
+
const state = readState(loopName)
|
|
286
|
+
if (state) writeState(loopName, { ...state, status: 'stopped', stoppedAt: new Date().toISOString() })
|
|
287
|
+
console.log(` ${loopName} (PID: ${pid}, 출처: ${source}) 종료`)
|
|
278
288
|
stopped = true
|
|
279
|
-
} catch {
|
|
280
|
-
console.log(` ${loopName} (PID: ${pid})
|
|
289
|
+
} catch (err) {
|
|
290
|
+
console.log(` ${loopName} (PID: ${pid}) 종료 실패: ${err.message}`)
|
|
291
|
+
console.log(` 수동 종료: kill -9 ${pid}`)
|
|
281
292
|
}
|
|
282
293
|
}
|
|
283
294
|
|
|
@@ -285,15 +296,18 @@ if (command === 'install') {
|
|
|
285
296
|
console.log(' 실행 중인 루프가 없습니다.')
|
|
286
297
|
}
|
|
287
298
|
} else if (subCmd === 'status') {
|
|
288
|
-
const
|
|
299
|
+
const tmuxAlive = isTmuxSessionAlive(sessionName)
|
|
289
300
|
const { readState } = await import('./state.js')
|
|
290
|
-
const reviewState = readState('review-loop')
|
|
291
|
-
const respondState = readState('respond-loop')
|
|
292
301
|
const mode = resolveLoopMode()
|
|
293
302
|
console.log(`\n 모드: ${mode}`)
|
|
294
|
-
console.log(` tmux 세션 (${sessionName}): ${
|
|
295
|
-
|
|
296
|
-
|
|
303
|
+
console.log(` tmux 세션 (${sessionName}): ${tmuxAlive ? '실행 중' : '없음'}`)
|
|
304
|
+
for (const loopName of ['review-loop', 'respond-loop']) {
|
|
305
|
+
const state = readState(loopName)
|
|
306
|
+
const found = findLoopPid(loopName)
|
|
307
|
+
const pidInfo = found ? `PID: ${found.pid} (${found.source}, 실행 중)` : (state?.pid ? `PID: ${state.pid} (사망)` : '프로세스 없음')
|
|
308
|
+
console.log(` ${loopName}: ${state?.status ?? 'unknown'} — ${pidInfo}`)
|
|
309
|
+
}
|
|
310
|
+
console.log('')
|
|
297
311
|
} else {
|
|
298
312
|
console.error('사용법: bylane loop <start|stop|status>')
|
|
299
313
|
process.exit(1)
|
package/src/loop-utils.js
CHANGED
|
@@ -47,6 +47,42 @@ export function killExistingLoop(loopName, stateDir = '.bylane/state') {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* 루프 프로세스의 PID를 반환한다.
|
|
52
|
+
* state 파일 → pgrep fallback 순서로 탐색.
|
|
53
|
+
* @param {string} loopName e.g. 'review-loop'
|
|
54
|
+
* @param {string} stateDir
|
|
55
|
+
* @returns {{ pid: number, source: 'state' | 'pgrep' } | null}
|
|
56
|
+
*/
|
|
57
|
+
export function findLoopPid(loopName, stateDir = '.bylane/state') {
|
|
58
|
+
// 1) state 파일에서 PID 확인
|
|
59
|
+
const state = readState(loopName, stateDir)
|
|
60
|
+
if (state?.pid) {
|
|
61
|
+
const pid = Number(state.pid)
|
|
62
|
+
try {
|
|
63
|
+
process.kill(pid, 0)
|
|
64
|
+
return { pid, source: 'state' }
|
|
65
|
+
} catch {
|
|
66
|
+
// PID가 있지만 이미 죽었으면 pgrep으로 재시도
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 2) pgrep으로 프로세스명 검색
|
|
71
|
+
const scriptFile = `${loopName}.js`
|
|
72
|
+
try {
|
|
73
|
+
const result = execSync(`pgrep -f "${scriptFile}"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
|
|
74
|
+
if (result) {
|
|
75
|
+
// 여러 PID가 있을 수 있음 — 첫 번째 사용
|
|
76
|
+
const pid = Number(result.split('\n')[0])
|
|
77
|
+
if (pid && pid !== process.pid) return { pid, source: 'pgrep' }
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// pgrep 결과 없음
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
|
|
50
86
|
/**
|
|
51
87
|
* tmux 세션이 살아있는지 확인
|
|
52
88
|
* @param {string} sessionName
|