@cocorograph/hub-agent 0.6.25 → 0.6.26

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.25",
3
+ "version": "0.6.26",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -195,10 +195,15 @@ class ClaudeStreamSession {
195
195
 
196
196
  /** 改修3: per-message セッションで busy 中に届いた送信を退避する pending キュー。
197
197
  * 常駐 query 化 (改修2) とは別レイヤー。resume チェーンは維持したまま、現ターン
198
- * 完了時 (finally) に先頭から drain して次ターンを自動発火する。 */
198
+ * 完了時 (finally) に先頭から drain して次ターンを自動発火する。
199
+ * キャンセル機能 (0.6.26): 各項目に安定 id を振り、browser から id 指定で
200
+ * 個別削除できるようにする。要素は { id: string, text: string }。 */
199
201
  this._pendingMessages = []
200
- /** 改修3: 直近 browser へ通知した pending 件数 (変化時のみ queue_state を emit する)。 */
201
- this._lastEmittedQueueCount = 0
202
+ /** キャンセル機能: pending 項目へ安定 id を振るための連番カウンタ。 */
203
+ this._queueSeq = 0
204
+ /** 改修3: 直近 browser へ通知した queue 署名 (件数 + id 列)。変化時のみ emit する。
205
+ * 空キューの署名 ("0:") で初期化し、空→空の冗長 emit を抑止する。 */
206
+ this._lastEmittedQueueSig = "0:"
202
207
 
203
208
  /** @type {Map<string, {resolve: (decision: object) => void}>} permission 応答待ち */
204
209
  this._permissionResolvers = new Map()
@@ -420,7 +425,7 @@ class ClaudeStreamSession {
420
425
  // 次を push すると SDK streaming-input で割り込み扱いになり得るため (公式 interrupt 警告)。
421
426
  // pending は queue_state で UI (送信待ちチップ) に出す (per-message と同じ体験)。
422
427
  if (this._busy) {
423
- this._pendingMessages.push(prompt)
428
+ this._enqueuePending(prompt)
424
429
  this.logger?.info(
425
430
  { stream_id: this.stream_id, queued: this._pendingMessages.length },
426
431
  "resident busy, message queued",
@@ -444,7 +449,7 @@ class ClaudeStreamSession {
444
449
  // 改修3: busy 中の送信は破棄せず pending キューへ退避し、現ターン完了時に drain する
445
450
  // (ターミナル流の「積む→待機→順次実行」)。常駐 query 化はしないので暴走リスクは増えない。
446
451
  if (this._busy) {
447
- this._pendingMessages.push(prompt)
452
+ this._enqueuePending(prompt)
448
453
  this.logger?.info(
449
454
  { stream_id: this.stream_id, queued: this._pendingMessages.length },
450
455
  "claude busy, message queued",
@@ -573,8 +578,8 @@ class ClaudeStreamSession {
573
578
  return
574
579
  }
575
580
  const next = this._pendingMessages.shift()
576
- this._emitQueueState()
577
- this._runPerMessage(next).catch((err) => {
581
+ this._emitQueueState([next.text])
582
+ this._runPerMessage(next.text).catch((err) => {
578
583
  this.logger?.error(
579
584
  { stream_id: this.stream_id, err: err?.message },
580
585
  "drain runPerMessage threw",
@@ -582,20 +587,53 @@ class ClaudeStreamSession {
582
587
  })
583
588
  }
584
589
 
590
+ /** キャンセル機能 (0.6.26): pending キューへ安定 id 付きで 1 件積む。 */
591
+ _enqueuePending(prompt) {
592
+ const id = `q${++this._queueSeq}`
593
+ this._pendingMessages.push({ id, text: prompt })
594
+ return id
595
+ }
596
+
597
+ /** キャンセル機能 (0.6.26): browser から id 指定で送信待ちメッセージを 1 件取り消す。
598
+ * 実行開始済み (既に drain された) メッセージは pending に残っていないので no-op になる。
599
+ * 削除に成功したら queue_state を再 emit して送信待ちチップを更新する。 */
600
+ cancelQueued(id) {
601
+ const before = this._pendingMessages.length
602
+ this._pendingMessages = this._pendingMessages.filter((m) => m.id !== id)
603
+ const removed = before !== this._pendingMessages.length
604
+ if (removed) {
605
+ this.logger?.info(
606
+ { stream_id: this.stream_id, id, queued: this._pendingMessages.length },
607
+ "queued message canceled",
608
+ )
609
+ this._emitQueueState()
610
+ }
611
+ return removed
612
+ }
613
+
585
614
  /** 改修3: pending キューの現状を browser へ通知する (送信待ちチップ表示用)。
586
- * onEvent 経由で claude.event(event.type="queue_state") として届く。 */
587
- _emitQueueState() {
615
+ * onEvent 経由で claude.event(event.type="queue_state") として届く。
616
+ * @param {string[]} [started] このタイミングで pending から取り出して実行開始した
617
+ * メッセージ本文。drain 由来の emit でのみ渡す。frontend はこれを user バブルへ
618
+ * 昇格させる。キャンセル / 追加由来の emit では空 (昇格させない)。これにより
619
+ * 「先頭の pending をキャンセルした」のを「実行開始した」と誤認しなくなる (0.6.26)。 */
620
+ _emitQueueState(started = []) {
588
621
  const count = this._pendingMessages.length
589
- // 件数が変わらないなら通知しない (空→空の冗長 emit を抑止)。
590
- if (count === this._lastEmittedQueueCount) return
591
- this._lastEmittedQueueCount = count
622
+ // 署名 = 件数 + id 列。件数が同じでもキャンセルで中身が変われば通知する。
623
+ const sig = `${count}:${this._pendingMessages.map((m) => m.id).join(",")}`
624
+ // started があるときは drain なので、sig 変化が無くても (理論上起きないが) 通知する。
625
+ if (started.length === 0 && sig === this._lastEmittedQueueSig) return
626
+ this._lastEmittedQueueSig = sig
592
627
  try {
593
628
  // messages は全文を載せる。frontend は実行開始 (drain) 時にこれを user バブルへ
594
629
  // 昇格させるため、ここで切り詰めると本文が欠ける。チップの省略表示は CSS 側で行う。
630
+ // items は id 付きでキャンセルボタンの対象特定に使う (0.6.26)。
595
631
  this.onEvent?.({
596
632
  type: "queue_state",
597
633
  pending: count,
598
- messages: [...this._pendingMessages],
634
+ messages: this._pendingMessages.map((m) => m.text),
635
+ items: this._pendingMessages.map((m) => ({ id: m.id, text: m.text })),
636
+ started,
599
637
  })
600
638
  } catch {
601
639
  /* ignore */
@@ -623,8 +661,8 @@ class ClaudeStreamSession {
623
661
  }
624
662
  const next = this._pendingMessages.shift()
625
663
  this._busy = true
626
- this._inputQueue.push(toSDKUserMessage(next))
627
- this._emitQueueState()
664
+ this._inputQueue.push(toSDKUserMessage(next.text))
665
+ this._emitQueueState([next.text])
628
666
  }
629
667
 
630
668
  /** 改修2+4: 常駐 query を 1 回だけ起動し、streaming input キューから複数ターンを処理する。
@@ -712,8 +750,8 @@ class ClaudeStreamSession {
712
750
  const next = this._pendingMessages.shift()
713
751
  this._busy = true
714
752
  this._residentStarted = true
715
- this._inputQueue.push(toSDKUserMessage(next))
716
- this._emitQueueState()
753
+ this._inputQueue.push(toSDKUserMessage(next.text))
754
+ this._emitQueueState([next.text])
717
755
  this._startResidentQuery()
718
756
  }
719
757
  }
@@ -990,6 +1028,17 @@ export class ClaudeStreamBridge extends EventEmitter {
990
1028
  return true
991
1029
  }
992
1030
 
1031
+ /** キャンセル機能 (0.6.26): browser → 送信待ち (pending) メッセージを id 指定で取り消す。
1032
+ * 実行中ターンには影響しない (中断は interrupt を使う)。 */
1033
+ cancelQueued({ stream_id, id }) {
1034
+ const s = this.sessions.get(stream_id)
1035
+ if (!s) {
1036
+ this.logger?.warn({ stream_id, id }, "claude.queue.cancel but stream missing")
1037
+ return false
1038
+ }
1039
+ return s.cancelQueued(id)
1040
+ }
1041
+
993
1042
  /**
994
1043
  * セッション停止 (graceful)。実行中ターンは中断せず完走させ、完走後に
995
1044
  * onReap で Map から撤去する。アイドルなら即時撤去。
package/src/main.mjs CHANGED
@@ -844,6 +844,11 @@ async function dispatch(msg, ctx) {
844
844
  if (!ctx.claudeBridge) return
845
845
  ctx.claudeBridge.interrupt({ stream_id: msg.stream_id })
846
846
  return
847
+ case "claude.queue.cancel":
848
+ // 送信待ち (pending) メッセージを id 指定で取り消す (0.6.26)。
849
+ if (!ctx.claudeBridge) return
850
+ ctx.claudeBridge.cancelQueued({ stream_id: msg.stream_id, id: msg.id })
851
+ return
847
852
  case "claude.detach":
848
853
  if (!ctx.claudeBridge) return
849
854
  ctx.claudeBridge.detach({ stream_id: msg.stream_id })