@elyun/bylane 1.26.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 +32 -24
- 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,42 +264,50 @@ 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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
process.kill(pid, 'SIGTERM')
|
|
279
|
-
console.log(` ${loopName} (PID: ${pid}) 종료`)
|
|
280
|
-
} catch {
|
|
281
|
-
console.log(` ${loopName} (PID: ${pid}) 종료 실패`)
|
|
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
|
|
282
278
|
}
|
|
283
|
-
|
|
284
|
-
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const { pid, source } = found
|
|
283
|
+
try {
|
|
284
|
+
process.kill(pid, 'SIGTERM')
|
|
285
|
+
const state = readState(loopName)
|
|
286
|
+
if (state) writeState(loopName, { ...state, status: 'stopped', stoppedAt: new Date().toISOString() })
|
|
287
|
+
console.log(` ${loopName} (PID: ${pid}, 출처: ${source}) 종료`)
|
|
288
|
+
stopped = true
|
|
289
|
+
} catch (err) {
|
|
290
|
+
console.log(` ${loopName} (PID: ${pid}) 종료 실패: ${err.message}`)
|
|
291
|
+
console.log(` 수동 종료: kill -9 ${pid}`)
|
|
285
292
|
}
|
|
286
|
-
writeState(loopName, { ...state, status: 'stopped', stoppedAt: new Date().toISOString() })
|
|
287
|
-
stopped = true
|
|
288
293
|
}
|
|
289
294
|
|
|
290
295
|
if (!stopped) {
|
|
291
296
|
console.log(' 실행 중인 루프가 없습니다.')
|
|
292
297
|
}
|
|
293
298
|
} else if (subCmd === 'status') {
|
|
294
|
-
const
|
|
299
|
+
const tmuxAlive = isTmuxSessionAlive(sessionName)
|
|
295
300
|
const { readState } = await import('./state.js')
|
|
296
|
-
const reviewState = readState('review-loop')
|
|
297
|
-
const respondState = readState('respond-loop')
|
|
298
301
|
const mode = resolveLoopMode()
|
|
299
302
|
console.log(`\n 모드: ${mode}`)
|
|
300
|
-
console.log(` tmux 세션 (${sessionName}): ${
|
|
301
|
-
|
|
302
|
-
|
|
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('')
|
|
303
311
|
} else {
|
|
304
312
|
console.error('사용법: bylane loop <start|stop|status>')
|
|
305
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
|