@cocorograph/hub-agent 0.6.78 → 0.6.81
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/scripts/install.ps1 +33 -5
- package/src/main.mjs +18 -0
- package/src/service-install.mjs +81 -1
package/package.json
CHANGED
package/scripts/install.ps1
CHANGED
|
@@ -237,15 +237,33 @@ function Set-Systemd($distro) {
|
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
# Register a logon task that
|
|
240
|
+
# Register a logon task that keeps the distro ALIVE (so its systemd --user
|
|
241
|
+
# hub-agent stays online).
|
|
242
|
+
#
|
|
243
|
+
# 旧実装は `wsl ... /bin/true` で distro を一瞬起こすだけだった。これだと WSL2 の
|
|
244
|
+
# アイドルシャットダウンが数十秒で distro ごと畳んでしまい (agent.log に
|
|
245
|
+
# `SIGTERM shutting down` が出て `wsl -l -v` が Stopped になる)、hub-agent も停止して
|
|
246
|
+
# オフラインに戻る。systemd --user + linger は「distro が起動している間」しか効かない
|
|
247
|
+
# ため、distro 自体を起こし続ける常駐プロセスが必要。
|
|
248
|
+
#
|
|
249
|
+
# 対策: ログオン中ずっと `sleep infinity` を WSL 内で走らせて distro を起動状態に保つ。
|
|
250
|
+
# 併せて hub-agent.service を start しておく (enable + linger 済みなら冪等)。
|
|
241
251
|
function Register-BootTask($distro, $user) {
|
|
242
|
-
Write-Step "Registering autostart on logon (Task Scheduler)"
|
|
243
|
-
|
|
252
|
+
Write-Step "Registering autostart on logon (Task Scheduler, keep-alive)"
|
|
253
|
+
# distro を起動し続ける keep-alive。systemctl start は冪等 (既に active なら no-op)。
|
|
254
|
+
$keepalive = "systemctl --user start hub-agent.service 2>/dev/null; exec sleep infinity"
|
|
255
|
+
$action = New-ScheduledTaskAction -Execute "wsl.exe" `
|
|
256
|
+
-Argument "-d $distro -u $user -- bash -lc `"$keepalive`""
|
|
244
257
|
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
|
245
|
-
|
|
258
|
+
# ExecutionTimeLimit=0 で無制限 (既定 72h で停止されると distro が落ちる)。
|
|
259
|
+
# keep-alive が万一終了したら 1 分後に最大 3 回まで再起動。多重起動は抑止。
|
|
260
|
+
$set = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
|
|
261
|
+
-StartWhenAvailable -MultipleInstances IgnoreNew `
|
|
262
|
+
-RestartInterval (New-TimeSpan -Minutes 1) -RestartCount 3 `
|
|
263
|
+
-ExecutionTimeLimit (New-TimeSpan -Seconds 0)
|
|
246
264
|
Register-ScheduledTask -TaskName $TASK_NAME -Action $action -Trigger $trigger -Settings $set `
|
|
247
265
|
-RunLevel Limited -Force | Out-Null
|
|
248
|
-
Write-Ok "Logon task '$TASK_NAME' registered"
|
|
266
|
+
Write-Ok "Logon task '$TASK_NAME' registered (keeps WSL distro alive)"
|
|
249
267
|
}
|
|
250
268
|
|
|
251
269
|
# Run the existing install.sh inside WSL.
|
|
@@ -315,6 +333,16 @@ function Main {
|
|
|
315
333
|
# --- autostart ---
|
|
316
334
|
Register-BootTask $distro $user
|
|
317
335
|
|
|
336
|
+
# セットアップ完了時点でオンライン化まで完結させる: keep-alive タスクを今すぐ起動し、
|
|
337
|
+
# distro を起動状態に保つ (再ログオンを待たずに hub-agent が常駐する)。
|
|
338
|
+
Write-Step "Starting keep-alive now (online without re-logon)"
|
|
339
|
+
try {
|
|
340
|
+
Start-ScheduledTask -TaskName $TASK_NAME
|
|
341
|
+
Write-Ok "Keep-alive started; hub-agent should come online shortly"
|
|
342
|
+
} catch {
|
|
343
|
+
Write-Warn "Start-ScheduledTask failed ($($_.Exception.Message)); ログオンし直すと起動します"
|
|
344
|
+
}
|
|
345
|
+
|
|
318
346
|
Write-Host ""
|
|
319
347
|
Write-Ok "Setup complete. Check the online status in the Hub UI:"
|
|
320
348
|
Write-Host " https://hub.cocorograph.com/user/cockpit/agents" -ForegroundColor Cyan
|
package/src/main.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import { WsClient } from "./ws-client.mjs"
|
|
|
25
25
|
import { PtyBridge } from "./pty-bridge.mjs"
|
|
26
26
|
import { ClaudeStreamBridge } from "./claude-stream-bridge.mjs"
|
|
27
27
|
import { UploadManager } from "./claude-upload.mjs"
|
|
28
|
+
import { requestSelfUninstall } from "./service-install.mjs"
|
|
28
29
|
import {
|
|
29
30
|
decideSessionRotation,
|
|
30
31
|
fetchSessionHistory,
|
|
@@ -656,6 +657,8 @@ export async function startDaemon({ version, ptyModule, claudeSdk } = {}) {
|
|
|
656
657
|
}
|
|
657
658
|
process.on("SIGINT", () => shutdown("SIGINT"))
|
|
658
659
|
process.on("SIGTERM", () => shutdown("SIGTERM"))
|
|
660
|
+
// dispatch から呼べるように shutdown を ctx に載せる (agent.shutdown 用)。
|
|
661
|
+
ctx.requestShutdown = shutdown
|
|
659
662
|
|
|
660
663
|
// 0.6.2 fix: 例外で silent 終了しないよう最後のセーフティネット。
|
|
661
664
|
// Node 24 で unhandledRejection は default で process を kill する仕様のため、
|
|
@@ -1136,6 +1139,21 @@ async function dispatch(msg, ctx) {
|
|
|
1136
1139
|
case "error":
|
|
1137
1140
|
ctx.logger.warn({ msg }, "hub error")
|
|
1138
1141
|
return
|
|
1142
|
+
case "agent.shutdown": {
|
|
1143
|
+
// Cockpit の「エージェント削除 (ゴミ箱)」由来。この端末をエージェントとして
|
|
1144
|
+
// 撤去する: サービス停止・無効化・autostart 撤去のみ。ユーザーデータ
|
|
1145
|
+
// (~/.hub / ~/.claude / プロジェクト) には触れない。
|
|
1146
|
+
ctx.logger.info({ msg }, "agent.shutdown received — self-uninstalling")
|
|
1147
|
+
try {
|
|
1148
|
+
// cgroup の外で動く detached プロセスにクリーンアップを委譲してから exit。
|
|
1149
|
+
requestSelfUninstall()
|
|
1150
|
+
} catch (err) {
|
|
1151
|
+
ctx.logger.warn({ err: err?.message }, "requestSelfUninstall failed")
|
|
1152
|
+
}
|
|
1153
|
+
// 本体は即停止 (detached cleanup が disable/stop を完走させる)。
|
|
1154
|
+
await ctx.requestShutdown?.("agent.shutdown")
|
|
1155
|
+
return
|
|
1156
|
+
}
|
|
1139
1157
|
case "pty.attach": {
|
|
1140
1158
|
const stream_id = msg.stream_id
|
|
1141
1159
|
try {
|
package/src/service-install.mjs
CHANGED
|
@@ -19,6 +19,9 @@ import { readConfig } from "./config.mjs"
|
|
|
19
19
|
|
|
20
20
|
const PLIST_LABEL = "co.cocorograph.hub-agent"
|
|
21
21
|
const SYSTEMD_UNIT_NAME = "hub-agent.service"
|
|
22
|
+
// Windows(WSL) のログオン keep-alive タスク名 (install.ps1 の $TASK_NAME と一致)。
|
|
23
|
+
// WSL からは Windows 連携 (interop) で schtasks.exe を叩いて削除できる。
|
|
24
|
+
const WSL_BOOT_TASK_NAME = "HubAgentWSLBoot"
|
|
22
25
|
|
|
23
26
|
function repoTemplatesDir() {
|
|
24
27
|
// src/service-install.mjs → ../templates
|
|
@@ -323,7 +326,14 @@ export async function installService({ bin } = {}) {
|
|
|
323
326
|
// 無い間 (= WSL2 のログオンタスク経由の boot や headless) に常駐しない。
|
|
324
327
|
const linger = enableLinger()
|
|
325
328
|
run("systemctl", ["--user", "daemon-reload"])
|
|
326
|
-
run("systemctl", ["--user", "enable",
|
|
329
|
+
run("systemctl", ["--user", "enable", SYSTEMD_UNIT_NAME])
|
|
330
|
+
// `enable --now` は既に起動中のサービスを再起動しない (--now は停止中なら start、
|
|
331
|
+
// 起動中なら no-op)。distro を起動し続ける keep-alive と組み合わさると、再 enroll で
|
|
332
|
+
// agent.json のトークンを書き換えても古いプロセスが旧トークンのまま生き続け、
|
|
333
|
+
// (同一ホスト重複解消で旧 agent 行が消えるため) WS 403 でオフラインになる。
|
|
334
|
+
// restart は停止中なら起動・起動中なら再起動するので、install-service のたびに
|
|
335
|
+
// 必ず最新の agent.json でプロセスを入れ替える。
|
|
336
|
+
run("systemctl", ["--user", "restart", SYSTEMD_UNIT_NAME])
|
|
327
337
|
return {
|
|
328
338
|
platform: "linux",
|
|
329
339
|
path: dest,
|
|
@@ -409,6 +419,75 @@ export async function uninstallService() {
|
|
|
409
419
|
throw new Error(`unsupported platform: ${process.platform}`)
|
|
410
420
|
}
|
|
411
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Cockpit の「エージェント削除 (ゴミ箱)」を受けて、この端末を **エージェントとして
|
|
424
|
+
* 撤去** する。サービス停止・無効化・autostart 撤去のみを行い、ユーザーデータ
|
|
425
|
+
* (`~/.hub` / `~/.claude` / プロジェクト) には一切触れない。
|
|
426
|
+
*
|
|
427
|
+
* 重要: hub-agent.service は `Restart=always`。自プロセスから `systemctl --user
|
|
428
|
+
* stop` すると自分の cgroup ごと殺され、後続のクリーンアップが完走しない。そこで
|
|
429
|
+
* **サービスの cgroup の外** で動く detached プロセスにクリーンアップを委譲し、
|
|
430
|
+
* 本体はすぐ exit する (呼び出し側が exit する)。
|
|
431
|
+
*
|
|
432
|
+
* 撤去内容:
|
|
433
|
+
* - systemd user unit: disable --now + unit ファイル削除 + daemon-reload
|
|
434
|
+
* - linger: loginctl disable-linger
|
|
435
|
+
* - Windows(WSL): schtasks.exe で keep-alive タスク削除 (interop。非WSLでは no-op)
|
|
436
|
+
*
|
|
437
|
+
* @param {{spawnSync?: typeof spawnSync}} [opts] テスト用に spawn を注入可能。
|
|
438
|
+
* @returns {boolean} クリーンアップ用 detached プロセスの起動に成功したら true。
|
|
439
|
+
*/
|
|
440
|
+
export function requestSelfUninstall(opts = {}) {
|
|
441
|
+
const spawn = opts.spawnSync ?? spawnSync
|
|
442
|
+
|
|
443
|
+
if (process.platform === "linux") {
|
|
444
|
+
const user = currentUsername()
|
|
445
|
+
const unit = linuxUnitPath()
|
|
446
|
+
// 1 秒待ってから (本体 exit を待つ) 撤去。各手順は失敗しても続行 (|| true)。
|
|
447
|
+
const script = [
|
|
448
|
+
"sleep 1",
|
|
449
|
+
`systemctl --user disable --now ${SYSTEMD_UNIT_NAME} 2>/dev/null || true`,
|
|
450
|
+
`rm -f '${unit}' 2>/dev/null || true`,
|
|
451
|
+
"systemctl --user daemon-reload 2>/dev/null || true",
|
|
452
|
+
`loginctl disable-linger ${user} 2>/dev/null || true`,
|
|
453
|
+
// WSL のみ有効 (Windows interop)。schtasks.exe が無い環境では黙って失敗。
|
|
454
|
+
`schtasks.exe /delete /tn ${WSL_BOOT_TASK_NAME} /f >/dev/null 2>&1 || true`,
|
|
455
|
+
].join("; ")
|
|
456
|
+
|
|
457
|
+
// systemd-run --user --scope で別 scope(=別 cgroup) に逃がす。これにより
|
|
458
|
+
// hub-agent.service を stop しても掃除プロセスは生き残って完走する。
|
|
459
|
+
const r = spawn(
|
|
460
|
+
"systemd-run",
|
|
461
|
+
["--user", "--scope", "--collect", "--quiet", "bash", "-lc", script],
|
|
462
|
+
{ stdio: "ignore", detached: true },
|
|
463
|
+
)
|
|
464
|
+
if (!r.error && r.status === 0) return true
|
|
465
|
+
// systemd-run が無い/失敗時は setsid で cgroup から外して代替。
|
|
466
|
+
const r2 = spawn("setsid", ["bash", "-lc", script], {
|
|
467
|
+
stdio: "ignore",
|
|
468
|
+
detached: true,
|
|
469
|
+
})
|
|
470
|
+
return !r2.error
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (process.platform === "darwin") {
|
|
474
|
+
const uid = ensureUnixUid()
|
|
475
|
+
const dest = macPlistPath()
|
|
476
|
+
// launchctl bootout は job の全プロセスを殺すため、setsid で job から切り離した
|
|
477
|
+
// detached プロセスに委譲する。
|
|
478
|
+
const script =
|
|
479
|
+
`sleep 1; launchctl bootout gui/${uid} '${dest}' 2>/dev/null || true; ` +
|
|
480
|
+
`rm -f '${dest}' 2>/dev/null || true`
|
|
481
|
+
const r = spawn("bash", ["-lc", `setsid bash -lc "${script}" >/dev/null 2>&1 &`], {
|
|
482
|
+
stdio: "ignore",
|
|
483
|
+
detached: true,
|
|
484
|
+
})
|
|
485
|
+
return !r.error
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return false
|
|
489
|
+
}
|
|
490
|
+
|
|
412
491
|
export const _internal = {
|
|
413
492
|
expandTemplate,
|
|
414
493
|
detectHubAgentBin,
|
|
@@ -420,4 +499,5 @@ export const _internal = {
|
|
|
420
499
|
bootstrapWithRetry,
|
|
421
500
|
currentUsername,
|
|
422
501
|
enableLinger,
|
|
502
|
+
requestSelfUninstall,
|
|
423
503
|
}
|