@cocorograph/hub-agent 0.7.43 → 0.7.45
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/claude-stream-bridge.mjs +45 -7
- package/src/main.mjs +8 -0
- package/src/ws-client.mjs +20 -0
package/package.json
CHANGED
|
@@ -306,8 +306,15 @@ class ClaudeStreamSession {
|
|
|
306
306
|
* キャンセル機能 (0.6.26): 各項目に安定 id を振り、browser から id 指定で
|
|
307
307
|
* 個別削除できるようにする。要素は { id: string, text: string }。 */
|
|
308
308
|
this._pendingMessages = []
|
|
309
|
-
/** キャンセル機能: pending 項目へ安定 id
|
|
309
|
+
/** キャンセル機能: pending 項目へ安定 id を振るための連番カウンタ (browser が
|
|
310
|
+
* input_id を渡さない旧経路のフォールバック用)。 */
|
|
310
311
|
this._queueSeq = 0
|
|
312
|
+
/** id 空間統一 (2026-07-08): キャンセルが投入より先着したときの取りこぼし防止。
|
|
313
|
+
* cancelQueued(id) された input_id をここに控え、後から届いた同 input_id の
|
|
314
|
+
* sendMessage を投入せず破棄する (楽観チップの×を押した直後に in-flight だった
|
|
315
|
+
* メッセージが後着して実行される「ゴースト実行」を防ぐ)。到着で消費 (delete)。
|
|
316
|
+
* 無限増加を避けるため上限で全クリアする (キャンセルは低頻度)。 */
|
|
317
|
+
this._canceledInputIds = new Set()
|
|
311
318
|
/** 改修3: 直近 browser へ通知した queue 署名 (件数 + id 列)。変化時のみ emit する。
|
|
312
319
|
* 空キューの署名 ("0:") で初期化し、空→空の冗長 emit を抑止する。 */
|
|
313
320
|
this._lastEmittedQueueSig = "0:"
|
|
@@ -667,6 +674,22 @@ class ClaudeStreamSession {
|
|
|
667
674
|
const prompt = extractPromptText(message)
|
|
668
675
|
if (!prompt) return { accepted: false, reason: "empty_message" }
|
|
669
676
|
|
|
677
|
+
// id 空間統一 (2026-07-08): browser が渡す input_id をキュー id として採用する
|
|
678
|
+
// (旧経路は input_id 無し → qN フォールバック)。これにより楽観チップ (input_id) と
|
|
679
|
+
// 権威キュー項目の id が一致し、確認前のキャンセルも効くようになる。
|
|
680
|
+
const inputId =
|
|
681
|
+
typeof opts.input_id === "string" && opts.input_id ? opts.input_id : null
|
|
682
|
+
// キャンセル先着レース: この input_id が既にキャンセル指示済みなら投入せず破棄する。
|
|
683
|
+
// accepted:true で返して browser の ackWatchdog 再送を止める (二重投入/ゴースト実行防止)。
|
|
684
|
+
if (inputId && this._canceledInputIds.has(inputId)) {
|
|
685
|
+
this._canceledInputIds.delete(inputId)
|
|
686
|
+
this.logger?.info(
|
|
687
|
+
{ stream_id: this.stream_id, input_id: inputId },
|
|
688
|
+
"dropped pre-canceled input (cancel arrived before input)",
|
|
689
|
+
)
|
|
690
|
+
return { accepted: true }
|
|
691
|
+
}
|
|
692
|
+
|
|
670
693
|
// ultracode ワンショット (0.6.28): このターンのみ xhigh effort + 常時 dynamic-workflow
|
|
671
694
|
// オーケストレーションを有効化する。セッション既定としては持たず (トークン消費が
|
|
672
695
|
// 桁違いになるため)、ターン単位でトグルし、完了後は通常状態へ戻す。
|
|
@@ -690,7 +713,7 @@ class ClaudeStreamSession {
|
|
|
690
713
|
// 次を push すると SDK streaming-input で割り込み扱いになり得るため (公式 interrupt 警告)。
|
|
691
714
|
// pending は queue_state で UI (送信待ちチップ) に出す (per-message と同じ体験)。
|
|
692
715
|
if (this._busy) {
|
|
693
|
-
this._enqueuePending(prompt, ultracode)
|
|
716
|
+
this._enqueuePending(prompt, ultracode, inputId)
|
|
694
717
|
this.logger?.info(
|
|
695
718
|
{ stream_id: this.stream_id, queued: this._pendingMessages.length },
|
|
696
719
|
"resident busy, message queued",
|
|
@@ -735,7 +758,7 @@ class ClaudeStreamSession {
|
|
|
735
758
|
// バイパスさせず、必ず pending 経由の FIFO へ合流させる (resident 側の FIFO 修正
|
|
736
759
|
// (PR #212) と同じ「pending を追い越させない」という設計思想)。
|
|
737
760
|
if (this._busy || this._pendingMessages.length > 0) {
|
|
738
|
-
this._enqueuePending(prompt, ultracode)
|
|
761
|
+
this._enqueuePending(prompt, ultracode, inputId)
|
|
739
762
|
this.logger?.info(
|
|
740
763
|
{ stream_id: this.stream_id, queued: this._pendingMessages.length },
|
|
741
764
|
"claude busy, message queued",
|
|
@@ -938,8 +961,11 @@ class ClaudeStreamSession {
|
|
|
938
961
|
|
|
939
962
|
/** キャンセル機能 (0.6.26): pending キューへ安定 id 付きで 1 件積む。
|
|
940
963
|
* ultracode (0.6.28): ワンショット ultracode フラグもエントリに保持し、drain 時に伝播する。 */
|
|
941
|
-
_enqueuePending(prompt, ultracode = false) {
|
|
942
|
-
|
|
964
|
+
_enqueuePending(prompt, ultracode = false, inputId = null) {
|
|
965
|
+
// id 空間統一 (2026-07-08): browser 由来の input_id があればそれをキュー id に使う
|
|
966
|
+
// (楽観チップの id と一致させ、確認前キャンセルを可能にする)。無ければ従来の qN。
|
|
967
|
+
const id =
|
|
968
|
+
typeof inputId === "string" && inputId ? inputId : `q${++this._queueSeq}`
|
|
943
969
|
this._pendingMessages.push({ id, text: prompt, ultracode: ultracode === true })
|
|
944
970
|
return id
|
|
945
971
|
}
|
|
@@ -948,6 +974,14 @@ class ClaudeStreamSession {
|
|
|
948
974
|
* 実行開始済み (既に drain された) メッセージは pending に残っていないので no-op になる。
|
|
949
975
|
* 削除に成功したら queue_state を再 emit して送信待ちチップを更新する。 */
|
|
950
976
|
cancelQueued(id) {
|
|
977
|
+
// id 空間統一 (2026-07-08): キャンセル先着レース対策。まだ投入されていない (in-flight の)
|
|
978
|
+
// input_id をキャンセルした場合に備え、id を控えて後着 sendMessage を破棄できるようにする
|
|
979
|
+
// (楽観チップの×を押した直後にメッセージが後着して実行される「ゴースト実行」を防ぐ)。
|
|
980
|
+
// 上限で全クリア (キャンセルは低頻度なので粗いバウンドで十分)。
|
|
981
|
+
if (typeof id === "string" && id) {
|
|
982
|
+
if (this._canceledInputIds.size >= 1024) this._canceledInputIds.clear()
|
|
983
|
+
this._canceledInputIds.add(id)
|
|
984
|
+
}
|
|
951
985
|
const before = this._pendingMessages.length
|
|
952
986
|
this._pendingMessages = this._pendingMessages.filter((m) => m.id !== id)
|
|
953
987
|
const removed = before !== this._pendingMessages.length
|
|
@@ -1571,7 +1605,7 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
1571
1605
|
* 「キュー/常駐 query への投入が完了した」ことを意味する (ターン完走まで待つと
|
|
1572
1606
|
* ack が大幅に遅延するため)。ターン実行自体の結果は従来どおり onEvent 経由で届く。
|
|
1573
1607
|
*/
|
|
1574
|
-
async input({ stream_id, message, ultracode }) {
|
|
1608
|
+
async input({ stream_id, message, ultracode, input_id }) {
|
|
1575
1609
|
const s = this.sessions.get(stream_id)
|
|
1576
1610
|
if (!s) {
|
|
1577
1611
|
this.logger?.warn({ stream_id }, "claude.input but stream missing")
|
|
@@ -1579,8 +1613,12 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
1579
1613
|
}
|
|
1580
1614
|
s.touch(stream_id) // B11: この端末の最終アクティビティを更新 (死端末 GC 用)
|
|
1581
1615
|
// ultracode (0.6.28): このメッセージのみ ultracode ワンショットを適用するフラグ。
|
|
1616
|
+
// id 空間統一 (2026-07-08): input_id をキュー id として下流へ渡す。
|
|
1582
1617
|
try {
|
|
1583
|
-
const result = await s.sendMessage(message, {
|
|
1618
|
+
const result = await s.sendMessage(message, {
|
|
1619
|
+
ultracode: ultracode === true,
|
|
1620
|
+
input_id: typeof input_id === "string" && input_id ? input_id : null,
|
|
1621
|
+
})
|
|
1584
1622
|
if (result?.accepted) return { ok: true }
|
|
1585
1623
|
return { ok: false, reason: result?.reason || "queue_dead" }
|
|
1586
1624
|
} catch (err) {
|
package/src/main.mjs
CHANGED
|
@@ -2138,6 +2138,9 @@ export function handleClaudeChatInput(msg, ctx) {
|
|
|
2138
2138
|
// ultracode (0.6.28): browser がこのメッセージ単位で送るワンショット指定。
|
|
2139
2139
|
// true のときだけそのターンを xhigh effort + dynamic-workflow で実行する。
|
|
2140
2140
|
ultracode: msg.ultracode === true,
|
|
2141
|
+
// id 空間統一 (2026-07-08): input_id をキュー id として下流へ渡す (楽観チップの
|
|
2142
|
+
// 確認前キャンセルを可能にする)。
|
|
2143
|
+
input_id,
|
|
2141
2144
|
})
|
|
2142
2145
|
.then((result) => {
|
|
2143
2146
|
if (!input_id) return
|
|
@@ -2588,6 +2591,11 @@ async function dispatch(msg, ctx) {
|
|
|
2588
2591
|
type: "claude.ready",
|
|
2589
2592
|
stream_id,
|
|
2590
2593
|
resuming: info.resuming,
|
|
2594
|
+
// id 空間統一 (2026-07-08): この agent は browser の input_id をキュー id として
|
|
2595
|
+
// 採用し、楽観送信待ちアイテムの確認前キャンセルに対応することを通知する capability。
|
|
2596
|
+
// 旧 agent にはこのフラグが無く、browser は楽観アイテムの×を出さない (ゴースト
|
|
2597
|
+
// 実行防止のフォールバック)。
|
|
2598
|
+
supports_queue_input_id: true,
|
|
2591
2599
|
})
|
|
2592
2600
|
// S1 (2026-07): セッション状態の identity 正本化。TUI の claude.tui.bind
|
|
2593
2601
|
// (main.mjs 内 clearChatSignal 呼び出し箇所参照) と対称に、SDK チャットの
|
package/src/ws-client.mjs
CHANGED
|
@@ -86,6 +86,17 @@ const BUFFERED_CTRL_TYPES = new Set([
|
|
|
86
86
|
const CTRL_BUFFER_MAX_FRAMES = 200
|
|
87
87
|
const CTRL_BUFFER_MAX_AGE_MS = 30_000
|
|
88
88
|
|
|
89
|
+
/** SDK-P2 (2026-07-08): claude.event が「ephemeral な生成中 delta」かどうか。
|
|
90
|
+
* stream_event の content_block_delta (text_delta / thinking_delta) は高頻度で流れる
|
|
91
|
+
* ephemeral (liveText 用) であり、切断中に ctrl バッファへ積むと確定イベントを evict する。
|
|
92
|
+
* 確定イベント (assistant/result/user/system) と非 delta stream_event (message_start 等,
|
|
93
|
+
* 低頻度) はバッファ対象として残す。 */
|
|
94
|
+
function isEphemeralDeltaEvent(obj) {
|
|
95
|
+
const ev = obj?.event
|
|
96
|
+
if (!ev || ev.type !== "stream_event") return false
|
|
97
|
+
return ev.event?.type === "content_block_delta"
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
export class WsClient extends EventEmitter {
|
|
90
101
|
/**
|
|
91
102
|
* @param {{ hub_url: string, agent_id: string, agent_token: string }} config
|
|
@@ -260,6 +271,15 @@ export class WsClient extends EventEmitter {
|
|
|
260
271
|
return false
|
|
261
272
|
}
|
|
262
273
|
if (BUFFERED_CTRL_TYPES.has(type)) {
|
|
274
|
+
// SDK-P2 (2026-07-08): ephemeral な stream_event の content_block_delta (text/thinking
|
|
275
|
+
// delta) は ctrl バッファに積まない。切断中は生成が続くと大量の delta が流れ、200 frame
|
|
276
|
+
// の ctrl リングを埋め尽くして低頻度の確定イベント (assistant/result/user/system) を
|
|
277
|
+
// evict していた (再接続後に確定バブルが出ず「異常に遅い」S5 の真因)。delta は liveText
|
|
278
|
+
// 用の ephemeral で、確定 assistant イベント (これはバッファ対象) が全文を持つため、
|
|
279
|
+
// 切断中の delta を捨てても再接続後の表示は確定イベントで復元される。
|
|
280
|
+
if (type === "claude.event" && isEphemeralDeltaEvent(obj)) {
|
|
281
|
+
return false
|
|
282
|
+
}
|
|
263
283
|
this._bufferCtrl(obj)
|
|
264
284
|
return false
|
|
265
285
|
}
|