@cocorograph/hub-agent 0.6.13 → 0.6.15

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.13",
3
+ "version": "0.6.15",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -150,6 +150,13 @@ class ClaudeStreamSession {
150
150
  /** 常駐 query を起動済みか (二重起動防止)。 */
151
151
  this._residentStarted = false
152
152
 
153
+ /** 改修3: per-message セッションで busy 中に届いた送信を退避する pending キュー。
154
+ * 常駐 query 化 (改修2) とは別レイヤー。resume チェーンは維持したまま、現ターン
155
+ * 完了時 (finally) に先頭から drain して次ターンを自動発火する。 */
156
+ this._pendingMessages = []
157
+ /** 改修3: 直近 browser へ通知した pending 件数 (変化時のみ queue_state を emit する)。 */
158
+ this._lastEmittedQueueCount = 0
159
+
153
160
  /** @type {Map<string, {resolve: (decision: object) => void}>} permission 応答待ち */
154
161
  this._permissionResolvers = new Map()
155
162
  /** 現在ターン実行中か (多重 query 防止) */
@@ -271,7 +278,8 @@ class ClaudeStreamSession {
271
278
 
272
279
  /**
273
280
  * ユーザーメッセージ 1 件を 1 query() として実行する。
274
- * 既存ターン実行中は無視 (UI 側で送信を抑止する想定だが念のため)。
281
+ * 既存ターン実行中 (busy) は破棄せず pending キューへ退避し、現ターン完了時に drain する
282
+ * (改修3)。常駐 query 対象 (新規セッション) は InputQueue へ積む (改修2)。
275
283
  */
276
284
  async sendMessage(message) {
277
285
  if (this._closed) return
@@ -297,14 +305,23 @@ class ClaudeStreamSession {
297
305
  }
298
306
 
299
307
  // 従来 per-message: 1 メッセージ = 1 query (resume チェーン)。既存セッション (resume) 用。
308
+ // 改修3: busy 中の送信は破棄せず pending キューへ退避し、現ターン完了時に drain する
309
+ // (ターミナル流の「積む→待機→順次実行」)。常駐 query 化はしないので暴走リスクは増えない。
300
310
  if (this._busy) {
301
- this.logger?.warn(
302
- { stream_id: this.stream_id },
303
- "claude session busy, message ignored",
311
+ this._pendingMessages.push(prompt)
312
+ this.logger?.info(
313
+ { stream_id: this.stream_id, queued: this._pendingMessages.length },
314
+ "claude busy, message queued",
304
315
  )
316
+ this._emitQueueState()
305
317
  return
306
318
  }
319
+ return this._runPerMessage(prompt)
320
+ }
307
321
 
322
+ /** per-message 1 ターンを実行する (resume チェーン)。busy 中に届いた送信は sendMessage
323
+ * が pending キューへ退避し、本メソッドの finally で drain する。 */
324
+ async _runPerMessage(prompt) {
308
325
  this._busy = true
309
326
  this._abortController = new AbortController()
310
327
  let aborted = false
@@ -397,10 +414,52 @@ class ClaudeStreamSession {
397
414
  } finally {
398
415
  this.onReap?.()
399
416
  }
417
+ } else {
418
+ // 改修3: busy 中に積まれた pending を順次発火 (per-message resume チェーン維持)。
419
+ // 停止 (abort) 後も温存したキューはそのまま流す。
420
+ this._drainPending()
400
421
  }
401
422
  }
402
423
  }
403
424
 
425
+ /** 改修3: pending キューの先頭を取り出して次の per-message ターンを発火する。
426
+ * 各ターンの finally から呼ばれ、キューが空になるまで resume チェーンが続く。 */
427
+ _drainPending() {
428
+ if (this._closed || this._busy) return
429
+ if (this._pendingMessages.length === 0) {
430
+ this._emitQueueState()
431
+ return
432
+ }
433
+ const next = this._pendingMessages.shift()
434
+ this._emitQueueState()
435
+ this._runPerMessage(next).catch((err) => {
436
+ this.logger?.error(
437
+ { stream_id: this.stream_id, err: err?.message },
438
+ "drain runPerMessage threw",
439
+ )
440
+ })
441
+ }
442
+
443
+ /** 改修3: pending キューの現状を browser へ通知する (送信待ちチップ表示用)。
444
+ * onEvent 経由で claude.event(event.type="queue_state") として届く。 */
445
+ _emitQueueState() {
446
+ const count = this._pendingMessages.length
447
+ // 件数が変わらないなら通知しない (空→空の冗長 emit を抑止)。
448
+ if (count === this._lastEmittedQueueCount) return
449
+ this._lastEmittedQueueCount = count
450
+ try {
451
+ // messages は全文を載せる。frontend は実行開始 (drain) 時にこれを user バブルへ
452
+ // 昇格させるため、ここで切り詰めると本文が欠ける。チップの省略表示は CSS 側で行う。
453
+ this.onEvent?.({
454
+ type: "queue_state",
455
+ pending: count,
456
+ messages: [...this._pendingMessages],
457
+ })
458
+ } catch {
459
+ /* ignore */
460
+ }
461
+ }
462
+
404
463
  /** 改修2: 常駐 query を 1 回だけ起動し、streaming input キューから複数ターンを処理する。
405
464
  * system/init は最初の 1 回のみ (毎ターン init = 文脈/キャッシュ温め直しを解消)。各ターンは
406
465
  * result で _busy=false にするが query は継続し次 input を待つ。close (inputQueue.close) で
@@ -513,6 +572,8 @@ class ClaudeStreamSession {
513
572
  /** セッション終了。新規ターンを止め、実行中なら中断し、watcher も停止する。 */
514
573
  close() {
515
574
  this._closed = true
575
+ // 改修3: 未処理の pending を破棄 (セッション終了後に drain しないため)。
576
+ this._pendingMessages = []
516
577
  // 常駐 query は input キューを閉じて generator を終わらせ query を完走させる。
517
578
  if (this._inputQueue) this._inputQueue.close()
518
579
  this.abortTurn()