@cocorograph/hub-agent 0.6.89 → 0.6.91

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.89",
3
+ "version": "0.6.91",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -241,6 +241,11 @@ ensure_npm_user_prefix() {
241
241
  local cur_prefix
242
242
  cur_prefix="$(npm config get prefix 2>/dev/null || echo '')"
243
243
  if [[ "$cur_prefix" == "$HOME/.npm-global" ]]; then
244
+ # ワンライナー (bash -c) は非ログイン非対話で profile が読まれないため、
245
+ # 既存 prefix の再セットアップ時に $HOME/.npm-global/bin が PATH に乗らず
246
+ # 後段の `hub-agent` / `claude` 呼び出しが command not found になる事故があった。
247
+ # 実行中シェルの PATH を毎回明示的に整える。
248
+ export PATH="$HOME/.npm-global/bin:$PATH"
244
249
  color_ok "npm global prefix は既に $HOME/.npm-global"
245
250
  return 0
246
251
  fi
package/src/main.mjs CHANGED
@@ -1051,6 +1051,11 @@ export async function maybeSyncClaudeSettings(msg, ctx) {
1051
1051
  }
1052
1052
  }
1053
1053
 
1054
+ // Plan ε grace guard の猶予 (ms)。backend の集計タイムアウト (_STREAMS_SYNC_TIMEOUT_S=0.5s)
1055
+ // + browser の WS 再接続 + 再 attach を十分に超える値。これ以内に touch された stream は
1056
+ // 「再 attach 中で sync に間に合わなかっただけ」とみなして誤殺しない。10min GC より十分短い。
1057
+ const STREAMS_SYNC_GRACE_MS = 45_000
1058
+
1054
1059
  /**
1055
1060
  * Plan ε: `agent.streams.sync.response` を受けて backend ↔ agent の現存
1056
1061
  * stream_id を能動同期する。
@@ -1066,14 +1071,34 @@ export function handleStreamsSyncResponse(msg, ctx) {
1066
1071
  Array.isArray(msg?.active_stream_ids) ? msg.active_stream_ids : [],
1067
1072
  )
1068
1073
  const localIds = new Set(ctx.ptyBridge.list())
1074
+ // churn 退化スナップショット保護 (ack_lost 根治 / 2026-06-17 根因 rank3)。
1075
+ // backend は browser 群へ _STREAMS_SYNC_TIMEOUT_S(0.5s) だけ問い合わせて active set を
1076
+ // 集める。再接続バースト中 (本番 ~20s 間隔・code 1006) は browser がまだ再 attach できて
1077
+ // おらず空/部分的な set が返り、その一瞬を authoritative とみなして「ローカル全 stream が
1078
+ // 孤児」と誤判定し、生きている PTY を皆殺しにしていた (再 attach 直後の stream を 1 秒後に
1079
+ // 殺し、以後の入力が stream_missing → 数分単位の「送信待ち」固着。0.6.89 ログ最多 churn 源)。
1080
+ // 空 set は信用せず kill を一切行わない (真の孤児は 10min GC + maxStreams 上限が回収する)。
1081
+ const haveAuthoritativeActiveSet = activeIds.size > 0
1069
1082
  for (const sid of localIds) {
1070
- if (!activeIds.has(sid)) {
1071
- ctx.logger?.warn?.(
1072
- { stream_id: sid, request_id: msg?.request_id },
1073
- "Plan ε: orphan stream detected, detaching",
1083
+ if (activeIds.has(sid)) continue
1084
+ if (!haveAuthoritativeActiveSet) continue
1085
+ // grace guard: 直近 STREAMS_SYNC_GRACE_MS 以内に touch (attach/write/resize/pty 出力) された
1086
+ // stream は「browser が再接続中で 0.5s 集計に間に合わなかっただけ」の可能性が高い。猶予内は
1087
+ // 殺さず延命する。真に放置された孤児は猶予超過で従来どおり detach され、10min GC も backstop。
1088
+ const lastSeen = ctx.ptyBridge.getLastSeen?.(sid) ?? 0
1089
+ const idleMs = Date.now() - lastSeen
1090
+ if (lastSeen > 0 && idleMs < STREAMS_SYNC_GRACE_MS) {
1091
+ ctx.logger?.info?.(
1092
+ { stream_id: sid, idle_ms: idleMs, request_id: msg?.request_id },
1093
+ "Plan ε: skip detach (within grace, browser likely re-attaching)",
1074
1094
  )
1075
- ctx.ptyBridge.detach({ stream_id: sid })
1095
+ continue
1076
1096
  }
1097
+ ctx.logger?.warn?.(
1098
+ { stream_id: sid, request_id: msg?.request_id },
1099
+ "Plan ε: orphan stream detected, detaching",
1100
+ )
1101
+ ctx.ptyBridge.detach({ stream_id: sid })
1077
1102
  }
1078
1103
  for (const sid of activeIds) {
1079
1104
  if (!localIds.has(sid)) {
@@ -169,6 +169,17 @@ export class PtyBridge extends EventEmitter {
169
169
  this.lastSeenAt.set(stream_id, this._now())
170
170
  }
171
171
 
172
+ /**
173
+ * stream の最終 touch 時刻 (ms epoch) を返す。未登録 / 未 touch は 0。
174
+ * Plan ε (handleStreamsSyncResponse) の grace 判定で「直近 attach/write/resize/出力が
175
+ * あった stream は browser が再 attach 中なだけ」と見なして誤殺を防ぐために使う。
176
+ * @param {string} stream_id
177
+ * @returns {number}
178
+ */
179
+ getLastSeen(stream_id) {
180
+ return this.lastSeenAt.get(stream_id) ?? 0
181
+ }
182
+
172
183
  /**
173
184
  * `gcStaleMs` を超えて idle な stream を一括 reap する。
174
185
  *