@cocorograph/hub-agent 0.6.79 → 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/src/main.mjs +18 -0
- package/src/service-install.mjs +81 -1
package/package.json
CHANGED
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
|
}
|