@cocorograph/hub-agent 0.7.49 → 0.7.50
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 +107 -18
package/package.json
CHANGED
|
@@ -209,6 +209,24 @@ function extractPromptText(message) {
|
|
|
209
209
|
return ""
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
/**
|
|
213
|
+
* SDK-P0-3 (2026-07-10): SDK の権威的ターン終了シグナル。
|
|
214
|
+
* sdk.d.ts (SDKSessionStateChangedMessage) いわく
|
|
215
|
+
* "'idle' fires after heldBackResult flushes and the bg-agent do-while exits
|
|
216
|
+
* — authoritative turn-over signal."
|
|
217
|
+
* つまり result が流れるターンでは `result` → `idle` の順で届き、result が届かない
|
|
218
|
+
* ターン (held back のまま終わる等) でも idle は届く。frontend は SDK-P0-1 で既に
|
|
219
|
+
* idle をターン終端として扱っているが、agent 側 (resident query) は result しか見て
|
|
220
|
+
* おらず _busy が固着していた。その非対称を解消するための判定。
|
|
221
|
+
*/
|
|
222
|
+
function isSessionIdleEvent(msg) {
|
|
223
|
+
return (
|
|
224
|
+
msg?.type === "system" &&
|
|
225
|
+
msg?.subtype === "session_state_changed" &&
|
|
226
|
+
msg?.state === "idle"
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
212
230
|
/**
|
|
213
231
|
* 1 stream に対応する Claude セッション (per-message query モデル)。
|
|
214
232
|
*/
|
|
@@ -331,6 +349,18 @@ class ClaudeStreamSession {
|
|
|
331
349
|
this._permissionResolvers = new Map()
|
|
332
350
|
/** 現在ターン実行中か (多重 query 防止) */
|
|
333
351
|
this._busy = false
|
|
352
|
+
/** SDK-P0-3 (2026-07-10): 現ターンが既に終端処理済みか (result / idle / abort の
|
|
353
|
+
* いずれか 1 回だけ _busy=false + drain させるための冪等ガード)。resident query は
|
|
354
|
+
* 1 ターンにつき `result` と `session_state_changed:idle` の**両方**が流れうるため、
|
|
355
|
+
* 素朴に両方で drain すると二重 drain (FIFO 崩れ) を起こす。ターン開始時
|
|
356
|
+
* (_beginResidentTurn) に false へ戻す。 */
|
|
357
|
+
this._turnSettled = false
|
|
358
|
+
/** SDK-P0-3: 現ターンが 1 件でもストリームイベントを出したか。
|
|
359
|
+
* 「前ターンの遅延 idle が、drain で始まったばかりの次ターンを誤終了させる」レースの
|
|
360
|
+
* ガードに使う。generator のイベント順序は保証される (turn N の idle は turn N+1 の
|
|
361
|
+
* 最初のイベントより必ず先に届く) ため、`idle 到着時に現ターンがまだ何も出していない`
|
|
362
|
+
* = その idle は前ターンのもの、と判定できる。ターン開始時に false へ戻す。 */
|
|
363
|
+
this._turnProgressed = false
|
|
334
364
|
/** detach 済みフラグ (新規ターン受付停止) */
|
|
335
365
|
this._closed = false
|
|
336
366
|
/** 実行中ターンの完走を待ってからクローズする予約フラグ (graceful detach 用)。
|
|
@@ -721,7 +751,7 @@ class ClaudeStreamSession {
|
|
|
721
751
|
this._emitQueueState()
|
|
722
752
|
return { accepted: true }
|
|
723
753
|
}
|
|
724
|
-
this.
|
|
754
|
+
this._beginResidentTurn()
|
|
725
755
|
// 改修4 (A): 死亡ガード。常駐 query が未起動 or (エラー等で) 終了済み (_residentQuery=null)
|
|
726
756
|
// なら (再)起動する。_runResidentQuery は起動時 options.resume=this.sessionId で文脈を
|
|
727
757
|
// 復元するため、途中死からの復活でも過去コンテキストは失われない。
|
|
@@ -1067,6 +1097,40 @@ class ClaudeStreamSession {
|
|
|
1067
1097
|
}
|
|
1068
1098
|
}
|
|
1069
1099
|
|
|
1100
|
+
/** SDK-P0-3 (2026-07-10): resident の「ターン開始」を 1 箇所に集約する。
|
|
1101
|
+
* _busy を立てるのと同時に、そのターンの終端フラグ (_turnSettled) と進行フラグ
|
|
1102
|
+
* (_turnProgressed) を必ずリセットする。ターン開始は 3 箇所ある
|
|
1103
|
+
* (sendMessage の直接 push / _drainResidentPending / _runResidentQuery finally の
|
|
1104
|
+
* 異常再起動 push) ので、リセット漏れを構造的に防ぐためのヘルパー。 */
|
|
1105
|
+
_beginResidentTurn() {
|
|
1106
|
+
this._busy = true
|
|
1107
|
+
this._turnSettled = false
|
|
1108
|
+
this._turnProgressed = false
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/** SDK-P0-3 (2026-07-10): resident の「ターン終端」を 1 箇所に集約する (result / idle 共通)。
|
|
1112
|
+
* 同一ターンで 2 回以上呼ばれても最初の 1 回しか作用しない (_turnSettled ガード)。
|
|
1113
|
+
* これにより `result` → `idle` の順で両方届く通常ケースでも drain は 1 回だけになる。
|
|
1114
|
+
* @param {string} reason "result" | "idle" — ログ用
|
|
1115
|
+
* @param {(msg: string) => void} [denyPending] 未解決 permission を閉じるクロージャ
|
|
1116
|
+
*/
|
|
1117
|
+
_settleResidentTurn(reason, denyPending) {
|
|
1118
|
+
if (this._turnSettled) return
|
|
1119
|
+
this._turnSettled = true
|
|
1120
|
+
denyPending?.("turn ended")
|
|
1121
|
+
this._busy = false
|
|
1122
|
+
// 改修4 (A): シリアライズした pending があれば次の 1 件を InputQueue へ流す。
|
|
1123
|
+
// ultracode (0.6.28): _drainResidentPending は applyFlagSettings を await するため
|
|
1124
|
+
// async。for await ループ内からは fire-and-forget で呼ぶ (最初の await より前に
|
|
1125
|
+
// _beginResidentTurn が同期実行されるので、以降の sendMessage は busy を正しく観測する)。
|
|
1126
|
+
this._drainResidentPending().catch((err) =>
|
|
1127
|
+
this.logger?.warn(
|
|
1128
|
+
{ stream_id: this.stream_id, err: err?.message, reason },
|
|
1129
|
+
"drainResidentPending threw",
|
|
1130
|
+
),
|
|
1131
|
+
)
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1070
1134
|
/** 改修4 (A): ターン完了時に pending の先頭 1 件を InputQueue へ流す (ターンのシリアライズ)。
|
|
1071
1135
|
* queue_state を更新して送信待ちチップを drain させる (frontend がバブル昇格する)。
|
|
1072
1136
|
* ultracode (0.6.28): 次ターンの目標 ultracode 状態へ reconcile してから push する
|
|
@@ -1082,7 +1146,7 @@ class ClaudeStreamSession {
|
|
|
1082
1146
|
// _busy=true のまま残り、以降の drain が全て弾かれて pending が永久に積み上がる
|
|
1083
1147
|
// デッドロックになる。失敗時は取り出したメッセージを先頭へ戻し _busy を解除して、
|
|
1084
1148
|
// 次の drain がやり直せる状態に必ず回復する (例外は既存方針通り warn で握る)。
|
|
1085
|
-
this.
|
|
1149
|
+
this._beginResidentTurn()
|
|
1086
1150
|
try {
|
|
1087
1151
|
// ultracode reconcile (0.6.28) も await で例外を投げうるので try の中に含める。
|
|
1088
1152
|
await this._reconcileResidentUltracode(next.ultracode === true)
|
|
@@ -1100,8 +1164,10 @@ class ClaudeStreamSession {
|
|
|
1100
1164
|
|
|
1101
1165
|
/** 改修2+4: 常駐 query を 1 回だけ起動し、streaming input キューから複数ターンを処理する。
|
|
1102
1166
|
* system/init は最初の 1 回のみ (毎ターン init = 文脈/キャッシュ温め直しを解消)。各ターンは
|
|
1103
|
-
* result で _busy=false にするが query
|
|
1104
|
-
* generator が終わり query が完走する。
|
|
1167
|
+
* 終端シグナル (result / session_state_changed:idle) で _busy=false にするが query は継続し
|
|
1168
|
+
* 次 input を待つ。close (inputQueue.close) で generator が終わり query が完走する。
|
|
1169
|
+
* SDK-P0-3 (2026-07-10): 終端を result だけに頼ると、SDK が idle でターンを終えたときに
|
|
1170
|
+
* _busy が固着し、以降の送信が pending へ永久滞留する (T06052)。
|
|
1105
1171
|
* 改修4: resume セッションも対象。query 起動時 options.resume に過去 session_id を渡して
|
|
1106
1172
|
* 文脈を引き継ぐ (init は初回 1 回のみ)。continue は使わない (自動継続の暴走回避)。初回 input は
|
|
1107
1173
|
* sendMessage が積むユーザーの実メッセージなので「Continue from where you left off」は発生しない。
|
|
@@ -1153,17 +1219,34 @@ class ClaudeStreamSession {
|
|
|
1153
1219
|
if (msg?.type === "result") {
|
|
1154
1220
|
if (typeof msg.session_id === "string") this.sessionId = msg.session_id
|
|
1155
1221
|
// ターン完了: 未解決 permission を閉じ、次 input 受付へ (query は継続)。
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
//
|
|
1159
|
-
//
|
|
1160
|
-
//
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1222
|
+
this._settleResidentTurn("result", denyPending)
|
|
1223
|
+
} else if (isSessionIdleEvent(msg)) {
|
|
1224
|
+
// SDK-P0-3 (2026-07-10): result が届かずに idle でターンが終わるケースの救済。
|
|
1225
|
+
// これを見ていなかったため _busy が true で固着し、以降の送信が
|
|
1226
|
+
// _pendingMessages に退避されたまま (drain 契機の result が永久に来ないので)
|
|
1227
|
+
// 永久滞留していた ("resident busy, message queued" が出続ける / リロードで消える)。
|
|
1228
|
+
//
|
|
1229
|
+
// ガード: 「前ターンの遅延 idle」で新ターンを誤終了させない。result で settle →
|
|
1230
|
+
// drain → 次ターン開始 (_busy=true) の直後に、同じターンの idle が続けて届く
|
|
1231
|
+
// (SDK は result → idle の順に流す) ため、素朴に _busy=false にすると新ターンを
|
|
1232
|
+
// 撃ち抜き二重 drain / FIFO 崩れを起こす。generator のイベント順序は保証されるので
|
|
1233
|
+
// 「現ターンがまだ 1 件もイベントを出していない (_turnProgressed=false)」なら
|
|
1234
|
+
// その idle は前ターンのものと確定できる。
|
|
1235
|
+
if (this._busy && this._turnProgressed) {
|
|
1236
|
+
this._settleResidentTurn("idle", denyPending)
|
|
1237
|
+
// per-message の RC-4 と対称に、チャット信号 (サイドバードット) を
|
|
1238
|
+
// processing→waiting へ落とす。result 経由なら main.mjs が result を見て
|
|
1239
|
+
// waiting にするが、idle 単独ではその契機が無く 90s TTL まで固着するため。
|
|
1240
|
+
try {
|
|
1241
|
+
this.onTurnSettled?.()
|
|
1242
|
+
} catch {
|
|
1243
|
+
/* ignore */
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
} else {
|
|
1247
|
+
// 現ターンが「進行した」証跡。init / assistant / stream_event / user、および
|
|
1248
|
+
// session_state_changed の running / requires_action もここに含む。
|
|
1249
|
+
this._turnProgressed = true
|
|
1167
1250
|
}
|
|
1168
1251
|
try {
|
|
1169
1252
|
this._emit(msg)
|
|
@@ -1207,7 +1290,7 @@ class ClaudeStreamSession {
|
|
|
1207
1290
|
// close() するだけで参照を持ち越さないので、ここで差し替えても整合する。
|
|
1208
1291
|
this._inputQueue = new InputQueue()
|
|
1209
1292
|
const next = this._pendingMessages.shift()
|
|
1210
|
-
this.
|
|
1293
|
+
this._beginResidentTurn()
|
|
1211
1294
|
this._residentStarted = true
|
|
1212
1295
|
// ultracode (0.6.29): query を先に起動して _residentQuery を確定させてから
|
|
1213
1296
|
// reconcile → push する (sendMessage の通常経路と同順)。push してから起動すると
|
|
@@ -1237,6 +1320,11 @@ class ClaudeStreamSession {
|
|
|
1237
1320
|
} catch {
|
|
1238
1321
|
/* ignore */
|
|
1239
1322
|
}
|
|
1323
|
+
// SDK-P0-3 (2026-07-10): 中断も「このターンの終端」なので settled を立てる。
|
|
1324
|
+
// interrupt() 後に SDK が session_state_changed:idle を流しても、_turnSettled=true
|
|
1325
|
+
// (pending 無しで drain が早期 return するケース) か _turnProgressed=false
|
|
1326
|
+
// (drain が次ターンを開始したケース) のどちらかで必ず無視される。
|
|
1327
|
+
this._turnSettled = true
|
|
1240
1328
|
this._busy = false
|
|
1241
1329
|
// SDK-P0-2 (2026-07-07): resident 経路のターン終端イベント非対称の解消。
|
|
1242
1330
|
// per-message の abort (_runPerMessage finally) は合成 result (aborted_by_user) を
|
|
@@ -1248,8 +1336,9 @@ class ClaudeStreamSession {
|
|
|
1248
1336
|
// - 実行中ターンがあった (wasBusy) ときだけ emit する (未走行での空 abort に
|
|
1249
1337
|
// 「中断しました」フッターを出さない)。
|
|
1250
1338
|
// - close()/reap 経由の abort (_closed=true) では browser は既に離脱しているため抑止する。
|
|
1251
|
-
// - drain タイミングは変更しない (既存の同期 drain
|
|
1252
|
-
// 中断ターンの遅延 idle
|
|
1339
|
+
// - drain タイミングは変更しない (既存の同期 drain を維持する)。result+idle の二重 drain と
|
|
1340
|
+
// 中断ターンの遅延 idle による新ターン誤終了は、SDK-P0-3 の _turnSettled /
|
|
1341
|
+
// _turnProgressed ガードで潰してある (上の _turnSettled=true もその一部)。
|
|
1253
1342
|
// - 合成 result の uuid は重複排除 (isDuplicateEvent の result 署名判定) 回避のため必須。
|
|
1254
1343
|
if (wasBusy && !this._closed) {
|
|
1255
1344
|
try {
|