@cocorograph/hub-agent 0.8.11 → 0.8.12

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.8.11",
3
+ "version": "0.8.12",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -523,11 +523,12 @@ export class ClaudeStreamSession {
523
523
  this._permissionResolvers = new Map()
524
524
  /** 現在ターン実行中か (多重 query 防止) */
525
525
  this._busy = false
526
- /** SDK-P0-3 (2026-07-10): 現ターンが既に終端処理済みか (idle / abort の
526
+ /** SDK-P0-3 (2026-07-10): 現ターンが既に終端処理済みか (result / idle / abort の
527
527
  * いずれか 1 回だけ _busy=false + drain させるための冪等ガード)。resident query は
528
528
  * `result` の後にもバックグラウンド処理が続き、最後に権威的な
529
- * `session_state_changed:idle` が流れる。通常完了では idle だけが settle し、
530
- * 明示 interrupt だけが先行して settled を立てる。ターン開始時に false へ戻す。 */
529
+ * `session_state_changed:idle` が流れることがある。実 SDK では result 後に idle が
530
+ * 来ないターンもあるため、result / idle の先着 1 件だけが settle し、遅延 idle は
531
+ * _staleIdleExpected で次ターンから隔離する。ターン開始時に false へ戻す。 */
531
532
  this._turnSettled = false
532
533
  /** SDK-P0-3: 現ターンが 1 件でもストリームイベントを出したか。
533
534
  * 「前ターンの遅延 idle が、drain で始まったばかりの次ターンを誤終了させる」レースの
@@ -1630,10 +1631,11 @@ export class ClaudeStreamSession {
1630
1631
  }
1631
1632
  }
1632
1633
 
1633
- /** resident の権威的な「ターン終端」を 1 箇所に集約する。
1634
- * 通常完了は SDK の session_state_changed:idle だけが呼ぶ。result は最終結果の
1635
- * flush であり、バックグラウンド処理を含むターン全体の終了境界ではない。
1636
- * @param {string} reason "idle" — ログ用
1634
+ /** resident の「ターン終端」を 1 箇所に集約する。
1635
+ * SDK は result 後に session_state_changed:idle を返すことがあるが、実環境では
1636
+ * idle が欠落するターンもある。result / idle の先着を終端とし、result で畳んだ後の
1637
+ * 遅延 idle はガードで次ターンから隔離する。
1638
+ * @param {string} reason "result" | "idle" — ログ用
1637
1639
  * @param {(msg: string) => void} [denyPending] 未解決 permission を閉じるクロージャ
1638
1640
  */
1639
1641
  _settleResidentTurn(reason, denyPending) {
@@ -1641,6 +1643,10 @@ export class ClaudeStreamSession {
1641
1643
  this._turnSettled = true
1642
1644
  denyPending?.("turn ended")
1643
1645
  this._busy = false
1646
+ // result の後に同一ターンの idle が遅れて届く場合がある。pending drain は同期的に
1647
+ // 次ターンの busy を立てるため、その idle を新ターンへ誤帰属させないよう先にガードする。
1648
+ // idle 自身で畳んだ場合は既に消費済みなので張らない。
1649
+ if (reason === "result") this._armStaleIdleGuard()
1644
1650
  // 改修4 (A): シリアライズした pending があれば次の 1 件を InputQueue へ流す。
1645
1651
  // ultracode (0.6.28): _drainResidentPending は applyFlagSettings を await するため
1646
1652
  // async。for await ループ内からは fire-and-forget で呼ぶ (最初の await より前に
@@ -1766,12 +1772,12 @@ export class ClaudeStreamSession {
1766
1772
  }
1767
1773
  if (msg?.type === "result") {
1768
1774
  if (typeof msg.session_id === "string") this.sessionId = msg.session_id
1769
- // result は結果 flush でありターン終了ではない。SDK の型定義が権威的な
1770
- // turn-over signal とする session_state_changed:idle まで busy を維持し、
1771
- // pending を streaming input へ流さない。
1772
1775
  // 現ターンの result が届いた時点で前ターンの遅延 idle は順序保証により
1773
- // 既に通過済みなので、interrupt 由来のガードだけ解除できる。
1776
+ // 既に通過済み。古いガードを解除し、result 用のガードを張り直して settle する。
1777
+ // 実 SDK では result 後に idle が来ないターンがあるため、idle 待ちだけにすると
1778
+ // _busy が永久に true のまま固着し、以後の入力が pending から drain されない。
1774
1779
  this._clearStaleIdleGuard()
1780
+ this._settleResidentTurn("result", denyPending)
1775
1781
  } else if (isSessionIdleEvent(msg)) {
1776
1782
  // SDK-P0-3 (2026-07-10): result が届かずに idle でターンが終わるケースの救済。
1777
1783
  // これを見ていなかったため _busy が true で固着し、以降の送信が
@@ -2282,7 +2288,12 @@ export class ClaudeStreamBridge extends EventEmitter {
2282
2288
  },
2283
2289
  "claude stream reattached to live session",
2284
2290
  )
2285
- return { stream_id, resuming: true, reattached: true }
2291
+ return {
2292
+ stream_id,
2293
+ resuming: true,
2294
+ reattached: true,
2295
+ turn_active: live._busy === true,
2296
+ }
2286
2297
  }
2287
2298
  }
2288
2299
  const session = new ClaudeStreamSession({
@@ -2395,7 +2406,11 @@ export class ClaudeStreamBridge extends EventEmitter {
2395
2406
  )
2396
2407
  // per-message モデルでは attach 時点では query() を起動しない (即 ready)。
2397
2408
  // 最初の input で query を起動する。
2398
- return { stream_id, resuming: !!resumeSessionId }
2409
+ return {
2410
+ stream_id,
2411
+ resuming: !!resumeSessionId,
2412
+ turn_active: session._busy === true,
2413
+ }
2399
2414
  }
2400
2415
 
2401
2416
  /**
package/src/main.mjs CHANGED
@@ -3433,6 +3433,10 @@ async function dispatch(msg, ctx) {
3433
3433
  type: "claude.ready",
3434
3434
  stream_id,
3435
3435
  resuming: info.resuming,
3436
+ // 再接続時の生成状態は resident session を保持する hub-agent が正本。
3437
+ // jsonl 履歴末尾の推測だけでは「実際は busy なのに UI は idle」となり、
3438
+ // 送信ボタンが楽観バブルを作る一方 agent はキューへ積む二重表示が起きる。
3439
+ turn_active: info.turn_active === true,
3436
3440
  history_pushed: resumeSessionId !== null,
3437
3441
  // id 空間統一 (2026-07-08): この agent は browser の input_id をキュー id として
3438
3442
  // 採用し、楽観送信待ちアイテムの確認前キャンセルに対応することを通知する capability。