@cocorograph/hub-agent 0.8.99 → 0.8.100
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 +48 -45
package/package.json
CHANGED
|
@@ -427,6 +427,9 @@ function isSessionRunningEvent(msg) {
|
|
|
427
427
|
* 正しく終端として扱う (ガードが固着して _busy が永久 true になる = T06052 の再発を防ぐ安全弁)。 */
|
|
428
428
|
const STALE_IDLE_GUARD_MS = 5000
|
|
429
429
|
|
|
430
|
+
// sibling CLI の既定待ち 10 秒より短く受理状態を待つ。超過は失敗と断定しない。
|
|
431
|
+
export const SIBLING_ACCEPT_WAIT_MS = 1000
|
|
432
|
+
|
|
430
433
|
/** per-message の title control request を待って InputQueue を閉じる上限。 */
|
|
431
434
|
const SESSION_TITLE_CLOSE_WAIT_MS = 15_000
|
|
432
435
|
|
|
@@ -1408,7 +1411,7 @@ export class ClaudeStreamSession {
|
|
|
1408
1411
|
"resident busy, message queued",
|
|
1409
1412
|
)
|
|
1410
1413
|
this._emitQueueState()
|
|
1411
|
-
return { accepted: true }
|
|
1414
|
+
return { accepted: true, queued: true }
|
|
1412
1415
|
}
|
|
1413
1416
|
if (this._closed) return { accepted: false, reason: "queue_dead" }
|
|
1414
1417
|
this._beginResidentTurn(prompt)
|
|
@@ -1460,7 +1463,7 @@ export class ClaudeStreamSession {
|
|
|
1460
1463
|
"claude busy, message queued",
|
|
1461
1464
|
)
|
|
1462
1465
|
this._emitQueueState()
|
|
1463
|
-
return { accepted: true }
|
|
1466
|
+
return { accepted: true, queued: true }
|
|
1464
1467
|
}
|
|
1465
1468
|
// Phase 2 (major#1): per-message idle 直接実行も started を出さないため、注入なら source 付きで
|
|
1466
1469
|
// 昇格バブルを作るよう started を先出しする (通常メッセージは no-op)。
|
|
@@ -3695,7 +3698,7 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
3695
3698
|
*
|
|
3696
3699
|
* @param {string} cwd
|
|
3697
3700
|
* @param {string} text
|
|
3698
|
-
* @returns {
|
|
3701
|
+
* @returns {object | Promise<object>} 配送先なし/曖昧は同期、配送先確定後は受理結果を待つ。
|
|
3699
3702
|
*/
|
|
3700
3703
|
sendToCwd(cwd, text, source) {
|
|
3701
3704
|
if (!cwd || !text) return { status: "not_found" }
|
|
@@ -3712,32 +3715,7 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
3712
3715
|
}
|
|
3713
3716
|
}
|
|
3714
3717
|
const { session, attached: att } = tier[0]
|
|
3715
|
-
|
|
3716
|
-
// handle を控え、失敗時にその entry だけを除去する (round2: text 除去は並行同一本文で誤消費)。
|
|
3717
|
-
const injectHandle = session._recordInjectSource?.(text, source)
|
|
3718
|
-
session
|
|
3719
|
-
.sendMessage({ role: "user", content: text })
|
|
3720
|
-
.then((r) => {
|
|
3721
|
-
// Phase 2 (major#3): sendMessage は throw せず {accepted:false} で失敗を返す経路がある
|
|
3722
|
-
// (queue_dead / empty_message / pre-canceled)。この場合は echo/started は来ないので
|
|
3723
|
-
// stale な live source entry を掃除し、**永続化もしない** (persist round1 major#1: 失敗を
|
|
3724
|
-
// 永続化すると 30 日 stale が残り後続の同文 human を誤カード化する)。
|
|
3725
|
-
if (r && r.accepted === false) {
|
|
3726
|
-
session._removeInjectSource?.(injectHandle)
|
|
3727
|
-
return
|
|
3728
|
-
}
|
|
3729
|
-
// 受理された (queued/started) ときだけ hydrate 用に永続化する (best-effort、live と独立)。
|
|
3730
|
-
recordInjectSourcePersistent(cwd, text, source).catch(() => {})
|
|
3731
|
-
})
|
|
3732
|
-
.catch((err) => {
|
|
3733
|
-
// 配送失敗 (throw) でも echo は来ないので stale な live entry を掃除する (永続化はしない)。
|
|
3734
|
-
session._removeInjectSource?.(injectHandle)
|
|
3735
|
-
this.logger?.error(
|
|
3736
|
-
{ cwd, err: err?.message },
|
|
3737
|
-
"sendToCwd: sendMessage threw unexpectedly",
|
|
3738
|
-
)
|
|
3739
|
-
})
|
|
3740
|
-
return { status: "delivered", session: this._describeCandidate(session, att) }
|
|
3718
|
+
return this._sendSiblingMessage(cwd, text, source, session, att)
|
|
3741
3719
|
}
|
|
3742
3720
|
|
|
3743
3721
|
/**
|
|
@@ -3761,7 +3739,7 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
3761
3739
|
* @param {string} cwd 呼び出し元がトークン検証した cwd (この cwd のセッションにのみ注入)
|
|
3762
3740
|
* @param {string} sessionId 宛先 session_id (describeLiveSessions の session_id)
|
|
3763
3741
|
* @param {string} text
|
|
3764
|
-
* @returns {
|
|
3742
|
+
* @returns {object | Promise<object>} 配送先なし/曖昧は同期、配送先確定後は受理結果を待つ。
|
|
3765
3743
|
*/
|
|
3766
3744
|
sendToStream(cwd, sessionId, text, source) {
|
|
3767
3745
|
if (!cwd || !sessionId || !text) return { status: "not_found" }
|
|
@@ -3780,28 +3758,53 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
3780
3758
|
}
|
|
3781
3759
|
}
|
|
3782
3760
|
const { session, attached } = matches[0]
|
|
3783
|
-
|
|
3761
|
+
return this._sendSiblingMessage(cwd, text, source, session, attached)
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
/** 受理結果だけを有限時間待つ。遅れて確定した結果も source の後始末には使う。 */
|
|
3765
|
+
async _sendSiblingMessage(cwd, text, source, session, attached) {
|
|
3766
|
+
const target = this._describeCandidate(session, attached)
|
|
3767
|
+
const delivered = { status: "delivered", session: target }
|
|
3784
3768
|
const injectHandle = session._recordInjectSource?.(text, source)
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
if (r && r.accepted === false) {
|
|
3769
|
+
let timer
|
|
3770
|
+
const outcome = (async () => {
|
|
3771
|
+
try {
|
|
3772
|
+
const result = await session.sendMessage({ role: "user", content: text })
|
|
3773
|
+
if (result?.accepted === false) {
|
|
3791
3774
|
session._removeInjectSource?.(injectHandle)
|
|
3792
|
-
return
|
|
3775
|
+
return { status: "delivery_failed", session: target, reason: result.reason }
|
|
3793
3776
|
}
|
|
3794
|
-
//
|
|
3777
|
+
// 受理時のみ永続化する。timeout 後に受理が確定した場合も維持する。
|
|
3795
3778
|
recordInjectSourcePersistent(cwd, text, source).catch(() => {})
|
|
3796
|
-
|
|
3797
|
-
|
|
3779
|
+
if (result?.queued === true) {
|
|
3780
|
+
return { status: "delivery_queued", session: target, reason: "queued_behind_active_turn" }
|
|
3781
|
+
}
|
|
3782
|
+
return delivered
|
|
3783
|
+
} catch (err) {
|
|
3798
3784
|
session._removeInjectSource?.(injectHandle)
|
|
3799
3785
|
this.logger?.error(
|
|
3800
|
-
{ session_id: sessionId, err: err?.message },
|
|
3801
|
-
"
|
|
3786
|
+
{ cwd, session_id: session.sessionId, err: err?.message },
|
|
3787
|
+
"sibling sendMessage threw unexpectedly; delivery remains unconfirmed",
|
|
3802
3788
|
)
|
|
3803
|
-
|
|
3804
|
-
|
|
3789
|
+
return delivered
|
|
3790
|
+
}
|
|
3791
|
+
})()
|
|
3792
|
+
try {
|
|
3793
|
+
return await Promise.race([
|
|
3794
|
+
outcome,
|
|
3795
|
+
new Promise((resolve) => {
|
|
3796
|
+
timer = setTimeout(() => {
|
|
3797
|
+
this.logger?.warn(
|
|
3798
|
+
{ cwd, session_id: session.sessionId, timeout_ms: SIBLING_ACCEPT_WAIT_MS },
|
|
3799
|
+
"sibling sendMessage acceptance timed out; delivery remains unconfirmed",
|
|
3800
|
+
)
|
|
3801
|
+
resolve(delivered)
|
|
3802
|
+
}, SIBLING_ACCEPT_WAIT_MS)
|
|
3803
|
+
}),
|
|
3804
|
+
])
|
|
3805
|
+
} finally {
|
|
3806
|
+
clearTimeout(timer)
|
|
3807
|
+
}
|
|
3805
3808
|
}
|
|
3806
3809
|
|
|
3807
3810
|
/**
|