@cocorograph/hub-agent 0.7.102 → 0.7.103
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 +77 -49
package/package.json
CHANGED
|
@@ -530,15 +530,16 @@ export class ClaudeStreamSession {
|
|
|
530
530
|
/** マスク済みイベントを onEvent へ流す共通経路。秘匿情報 / 非公開スキルプロンプト本文を
|
|
531
531
|
* 表示・WS 配信前に伏せる。マスク失敗時は素のイベントを流す (表示が止まるより素通し優先)。 */
|
|
532
532
|
/** 連携カード種別 (Phase 2): 注入本文と source を FIFO に控える。sendToCwd/sendToStream が
|
|
533
|
-
* session.sendMessage()
|
|
533
|
+
* session.sendMessage() を呼ぶ直前に記録し、**drain 昇格時に _emitQueueState が started 本文
|
|
534
|
+
* 一致で消費**して startedSources として frontend へ載せる。
|
|
534
535
|
*
|
|
535
|
-
* ⚠️
|
|
536
|
-
*
|
|
537
|
-
* (
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
* @param {string} text 注入本文 (
|
|
536
|
+
* ⚠️ claude は SDK が user メッセージを live echo しない (reducer set_queue コメント参照)。
|
|
537
|
+
* user バブルは queue_state の started 昇格で作られるため、当初の _emit(user echo) タグ方式は
|
|
538
|
+
* claude で発火せず注入が青バブルのままだった (2026-07-23 実機で判明)。消費点を _emitQueueState
|
|
539
|
+
* に移した。相関は「注入本文の完全一致」(footer 込みで一意性が高い)。ID メタ伝播が堅牢だが
|
|
540
|
+
* started は本文しか持たないため本文相関を採る。衝突リスクは①送信失敗時の stale → _removeInjectSource
|
|
541
|
+
* で handle 同定除去、②未 drain の無限成長 → 32 件上限、で最小化。衝突時も誤タグ止まりで安全。
|
|
542
|
+
* @param {string} text 注入本文 (drain 時の started 本文と一致する)
|
|
542
543
|
* @param {'peer'|'orchestrator'|undefined} source
|
|
543
544
|
* @returns {object|null} この entry の固有 handle (identity)。配送失敗時に _removeInjectSource へ
|
|
544
545
|
* 渡して**その entry だけ**を除去する。無効な source/text では null。 */
|
|
@@ -563,30 +564,6 @@ export class ClaudeStreamSession {
|
|
|
563
564
|
if (idx !== -1) this._pendingInjectSources.splice(idx, 1)
|
|
564
565
|
}
|
|
565
566
|
|
|
566
|
-
/** user echo イベントの本文が控えた注入本文と一致すれば source タグを付けて返す (それ以外は
|
|
567
|
-
* 無変更)。frontend はこの source があれば専用の連携依頼カードへ分岐する。共有/at-rest の
|
|
568
|
-
* イベントを壊さないよう shallow-clone に付ける (top-level に source を足すだけ)。 */
|
|
569
|
-
_tagInjectSource(event) {
|
|
570
|
-
if (!this._pendingInjectSources.length) return event
|
|
571
|
-
if (!event || event.type !== "user" || !event.message) return event
|
|
572
|
-
const content = event.message.content
|
|
573
|
-
const text =
|
|
574
|
-
typeof content === "string"
|
|
575
|
-
? content
|
|
576
|
-
: Array.isArray(content)
|
|
577
|
-
? content
|
|
578
|
-
.filter((b) => b && b.type === "text" && typeof b.text === "string")
|
|
579
|
-
.map((b) => b.text)
|
|
580
|
-
.join("\n")
|
|
581
|
-
: ""
|
|
582
|
-
if (!text) return event
|
|
583
|
-
const idx = this._pendingInjectSources.findIndex((p) => p.text === text)
|
|
584
|
-
if (idx === -1) return event
|
|
585
|
-
const { source } = this._pendingInjectSources[idx]
|
|
586
|
-
this._pendingInjectSources.splice(idx, 1)
|
|
587
|
-
return { ...event, source }
|
|
588
|
-
}
|
|
589
|
-
|
|
590
567
|
_emit(event) {
|
|
591
568
|
if (!this.onEvent) return
|
|
592
569
|
let out = event
|
|
@@ -601,7 +578,6 @@ export class ClaudeStreamSession {
|
|
|
601
578
|
out = event
|
|
602
579
|
}
|
|
603
580
|
}
|
|
604
|
-
out = this._tagInjectSource(out)
|
|
605
581
|
this.onEvent(out)
|
|
606
582
|
}
|
|
607
583
|
|
|
@@ -1026,6 +1002,9 @@ export class ClaudeStreamSession {
|
|
|
1026
1002
|
// 初回ターンの ultracode は起動時 options.settings に焼き込むため、起動前 reconcile
|
|
1027
1003
|
// (applyFlagSettings) のための空窓も不要。
|
|
1028
1004
|
if (!this._residentQuery || this._residentQueryReturned) {
|
|
1005
|
+
// Phase 2 (major#1): resident fresh start は started を出さないため、注入なら source 付きで
|
|
1006
|
+
// 昇格バブルを作るよう started を先出しする (通常メッセージは no-op)。
|
|
1007
|
+
this._maybeEmitInjectStarted(prompt)
|
|
1029
1008
|
this._startResidentTurnWithInput(toSDKUserMessage(prompt), ultracode)
|
|
1030
1009
|
return { accepted: true }
|
|
1031
1010
|
}
|
|
@@ -1038,6 +1017,11 @@ export class ClaudeStreamSession {
|
|
|
1038
1017
|
ultracode,
|
|
1039
1018
|
)
|
|
1040
1019
|
if (outcome === "closed") return { accepted: false, reason: "queue_dead" }
|
|
1020
|
+
// Phase 2 (round2 major): 起動済み・idle な resident query への delivery は
|
|
1021
|
+
// _reconcileThenDeliver 内で pushed (inputQueue.push) / restarted
|
|
1022
|
+
// (_startResidentTurnWithInput) のどちらも started を出さない。注入なら source 付き
|
|
1023
|
+
// started をここで先出しして昇格バブルをカード化する (通常メッセージは no-op)。
|
|
1024
|
+
this._maybeEmitInjectStarted(prompt)
|
|
1041
1025
|
return { accepted: true }
|
|
1042
1026
|
}
|
|
1043
1027
|
|
|
@@ -1058,6 +1042,9 @@ export class ClaudeStreamSession {
|
|
|
1058
1042
|
this._emitQueueState()
|
|
1059
1043
|
return { accepted: true }
|
|
1060
1044
|
}
|
|
1045
|
+
// Phase 2 (major#1): per-message idle 直接実行も started を出さないため、注入なら source 付きで
|
|
1046
|
+
// 昇格バブルを作るよう started を先出しする (通常メッセージは no-op)。
|
|
1047
|
+
this._maybeEmitInjectStarted(prompt)
|
|
1061
1048
|
// ack はターン完走を待たず「起動できたか」で返すため、フル実行の Promise を
|
|
1062
1049
|
// 呼び出し元へ伝播させない (fire-and-forget)。例外は _runPerMessage 内部で
|
|
1063
1050
|
// 握っているため通常は reject しないが、念のため warn だけ残す。
|
|
@@ -1304,6 +1291,20 @@ export class ClaudeStreamSession {
|
|
|
1304
1291
|
// force のときは再アタッチ snapshot なので重複チェックを完全にバイパスする。
|
|
1305
1292
|
if (!force && started.length === 0 && sig === this._lastEmittedQueueSig) return
|
|
1306
1293
|
this._lastEmittedQueueSig = sig
|
|
1294
|
+
// 連携カード種別 (Phase 2, 2026-07-23 修正): **claude は SDK が user メッセージを live echo
|
|
1295
|
+
// しない** (reducer set_queue のコメント参照)。ゆえに user バブルは queue_state の started
|
|
1296
|
+
// (drain 昇格) から作られる。当初 _emit の user echo にタグする実装は claude では発火せず
|
|
1297
|
+
// (dead code)、注入が青バブルのままだった (実機確認)。正しくは started 各テキストの source を
|
|
1298
|
+
// ここで _pendingInjectSources から引いて startedSources として載せ、frontend が昇格バブルへ
|
|
1299
|
+
// source を付ける。started は全 drain 経路 (_drainPending / resident / per-message finally) が
|
|
1300
|
+
// 通る funnel なのでここ 1 箇所で全経路をカバーする。注入本文 (footer 込み) の完全一致で消費。
|
|
1301
|
+
const startedSources = started.map((text) => {
|
|
1302
|
+
const idx = this._pendingInjectSources.findIndex((p) => p.text === text)
|
|
1303
|
+
if (idx === -1) return null
|
|
1304
|
+
const { source } = this._pendingInjectSources[idx]
|
|
1305
|
+
this._pendingInjectSources.splice(idx, 1)
|
|
1306
|
+
return source
|
|
1307
|
+
})
|
|
1307
1308
|
try {
|
|
1308
1309
|
// messages は全文を載せる。frontend は実行開始 (drain) 時にこれを user バブルへ
|
|
1309
1310
|
// 昇格させるため、ここで切り詰めると本文が欠ける。チップの省略表示は CSS 側で行う。
|
|
@@ -1314,12 +1315,25 @@ export class ClaudeStreamSession {
|
|
|
1314
1315
|
messages: this._pendingMessages.map((m) => m.text),
|
|
1315
1316
|
items: this._pendingMessages.map((m) => ({ id: m.id, text: m.text })),
|
|
1316
1317
|
started,
|
|
1318
|
+
// started と parallel。連携注入なら 'peer'|'orchestrator'、通常は null。
|
|
1319
|
+
startedSources,
|
|
1317
1320
|
})
|
|
1318
1321
|
} catch {
|
|
1319
1322
|
/* ignore */
|
|
1320
1323
|
}
|
|
1321
1324
|
}
|
|
1322
1325
|
|
|
1326
|
+
/** 連携カード種別 (Phase 2, cross-review major#1): **idle 直接送信経路**用。busy→queue→drain は
|
|
1327
|
+
* _emitQueueState([text]) を通るが、idle 直接実行 (resident fresh start / per-message 即時) は
|
|
1328
|
+
* started を出さないため、注入の source が startedSources に載らずカード化されない。ここで
|
|
1329
|
+
* 「その本文が注入 (source 記録済み) のときだけ」started を 1 件 emit して昇格バブルを source 付き
|
|
1330
|
+
* で作る。通常メッセージ (source 無し) は emit せず従来経路 (optimistic 等) に任せる = 挙動不変。 */
|
|
1331
|
+
_maybeEmitInjectStarted(prompt) {
|
|
1332
|
+
if (this._pendingInjectSources.some((p) => p.text === prompt)) {
|
|
1333
|
+
this._emitQueueState([prompt])
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1323
1337
|
/** 空 InputQueue 窓封鎖 (T06118): fresh start する resume 常駐 query を「入力を先に積んで
|
|
1324
1338
|
* から起動」する (push 先行)。fresh な InputQueue を作り、実ユーザーメッセージを push して
|
|
1325
1339
|
* から query を起動するため、「resume + 空入力」の窓で SDK が自動継続ターン (Continue from
|
|
@@ -2697,14 +2711,22 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
2697
2711
|
// 連携カード種別 (Phase 2): 配送先が確定した後に記録する (ambiguous/not_found では記録しない)。
|
|
2698
2712
|
// handle を控え、失敗時にその entry だけを除去する (round2: text 除去は並行同一本文で誤消費)。
|
|
2699
2713
|
const injectHandle = session._recordInjectSource?.(text, source)
|
|
2700
|
-
session
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2714
|
+
session
|
|
2715
|
+
.sendMessage({ role: "user", content: text })
|
|
2716
|
+
.then((r) => {
|
|
2717
|
+
// Phase 2 (major#3): sendMessage は throw せず {accepted:false} で失敗を返す経路がある
|
|
2718
|
+
// (queue_dead / empty_message / pre-canceled)。この場合も echo/started は来ないので
|
|
2719
|
+
// stale source entry を掃除する (誤タグ防止)。
|
|
2720
|
+
if (r && r.accepted === false) session._removeInjectSource?.(injectHandle)
|
|
2721
|
+
})
|
|
2722
|
+
.catch((err) => {
|
|
2723
|
+
// 配送失敗 (throw) でも echo は来ないので stale な source entry を掃除する。
|
|
2724
|
+
session._removeInjectSource?.(injectHandle)
|
|
2725
|
+
this.logger?.error(
|
|
2726
|
+
{ cwd, err: err?.message },
|
|
2727
|
+
"sendToCwd: sendMessage threw unexpectedly",
|
|
2728
|
+
)
|
|
2729
|
+
})
|
|
2708
2730
|
return { status: "delivered", session: this._describeCandidate(session, att) }
|
|
2709
2731
|
}
|
|
2710
2732
|
|
|
@@ -2750,14 +2772,20 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
2750
2772
|
const { session, attached } = matches[0]
|
|
2751
2773
|
// 連携カード種別 (Phase 2): 宛先確定後に記録 (not_found/ambiguous では記録しない)。
|
|
2752
2774
|
const injectHandle = session._recordInjectSource?.(text, source)
|
|
2753
|
-
session
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
)
|
|
2760
|
-
|
|
2775
|
+
session
|
|
2776
|
+
.sendMessage({ role: "user", content: text })
|
|
2777
|
+
.then((r) => {
|
|
2778
|
+
// Phase 2 (major#3): {accepted:false} (queue_dead / empty_message / pre-canceled) も
|
|
2779
|
+
// echo/started は来ないので stale source entry を掃除する (誤タグ防止)。
|
|
2780
|
+
if (r && r.accepted === false) session._removeInjectSource?.(injectHandle)
|
|
2781
|
+
})
|
|
2782
|
+
.catch((err) => {
|
|
2783
|
+
session._removeInjectSource?.(injectHandle)
|
|
2784
|
+
this.logger?.error(
|
|
2785
|
+
{ session_id: sessionId, err: err?.message },
|
|
2786
|
+
"sendToStream: sendMessage threw unexpectedly",
|
|
2787
|
+
)
|
|
2788
|
+
})
|
|
2761
2789
|
return { status: "delivered", session: this._describeCandidate(session, attached) }
|
|
2762
2790
|
}
|
|
2763
2791
|
|