@cocorograph/hub-agent 0.6.1 → 0.6.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
package/src/main.mjs CHANGED
@@ -230,6 +230,16 @@ export async function startDaemon({ version, ptyModule, claudeSdk } = {}) {
230
230
 
231
231
  client.connect()
232
232
 
233
+ // 0.6.2 fix: event loop を ref されたまま生かしておく watchdog。
234
+ // hub-agent の他の background task (heartbeat / reconnect / state loop / fs.watch)
235
+ // は全て .unref() / persistent:false なので、長時間アイドル + WS 切断 + pty 全 GC
236
+ // が同時発生すると Node が「やることなし」と判断して exit(0) で silent 終了する
237
+ // 既存バグがあった。watchdog を 1 つ ref のまま持つことで exit を防ぐ。
238
+ // shutdown 時に clearInterval して正常終了を許可する。
239
+ const keepaliveTimer = setInterval(() => {
240
+ // no-op. ref されていることだけが重要。
241
+ }, 60_000)
242
+
233
243
  // 5s 周期で全 tmux session の状態を捕捉し、変化したものだけ session.state を push。
234
244
  // browser がフォーカスしていない session でも常時更新するためのバックグラウンド職人。
235
245
  const stateLoop = startStateLoop({ client, plugins, logger, intervalMs: 5_000 })
@@ -246,11 +256,30 @@ export async function startDaemon({ version, ptyModule, claudeSdk } = {}) {
246
256
  ptyBridge.shutdown()
247
257
  claudeBridge?.shutdown?.()
248
258
  client.stop()
259
+ // 0.6.2 fix: watchdog を解放して event loop を抜けられるようにする
260
+ clearInterval(keepaliveTimer)
249
261
  process.exit(0)
250
262
  }
251
263
  process.on("SIGINT", () => shutdown("SIGINT"))
252
264
  process.on("SIGTERM", () => shutdown("SIGTERM"))
253
265
 
266
+ // 0.6.2 fix: 例外で silent 終了しないよう最後のセーフティネット。
267
+ // Node 24 で unhandledRejection は default で process を kill する仕様のため、
268
+ // warn ログだけ出して継続させる (本来の原因は別途修正必要だが、agent が落ちて
269
+ // ユーザー作業が止まる方が痛い)。
270
+ process.on("unhandledRejection", (reason, promise) => {
271
+ logger.error(
272
+ { reason: String(reason), stack: reason?.stack },
273
+ "unhandledRejection (agent kept alive by watchdog)",
274
+ )
275
+ })
276
+ process.on("uncaughtException", (err) => {
277
+ logger.error(
278
+ { err: err?.message, stack: err?.stack },
279
+ "uncaughtException (agent kept alive by watchdog)",
280
+ )
281
+ })
282
+
254
283
  return { client, plugins, ptyBridge, claudeBridge }
255
284
  }
256
285
 
@@ -15,13 +15,20 @@
15
15
  <key>RunAtLoad</key>
16
16
  <true/>
17
17
 
18
+ <!--
19
+ KeepAlive を無条件 true に変更 (0.6.2 fix)。
20
+ 旧設定 `SuccessfulExit=false` は「正常終了 (exit 0) なら再起動しない」だが、
21
+ hub-agent の全 background task (heartbeat / reconnect / state loop / fs.watch) が
22
+ .unref() されているため、長時間アイドル + WS 切断 + pty GC kill で全 ref が
23
+ 0 になり Node が exit(0) で「正常終了」する事象が頻発していた。launchctl が
24
+ 「正常終了だから restart 不要」と判断し、agent が静かに停止 → ユーザーは
25
+ 「offline」表示で気づく。本修正で「どんな exit code でも再起動」に変更し
26
+ ThrottleInterval=10s で spawn loop も防ぐ。
27
+ main.mjs 側にも watchdog setInterval を追加して exit 自体を起こさない設計に
28
+ したが、plist 側の defense in depth として KeepAlive も無条件化する。
29
+ -->
18
30
  <key>KeepAlive</key>
19
- <dict>
20
- <key>SuccessfulExit</key>
21
- <false/>
22
- <key>NetworkState</key>
23
- <true/>
24
- </dict>
31
+ <true/>
25
32
 
26
33
  <key>StandardOutPath</key>
27
34
  <string>__HOME__/.hub/agent.log</string>