@cocorograph/hub-agent 0.6.37 → 0.6.39
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 +38 -1
- package/src/main.mjs +20 -11
- package/src/ws-client.mjs +41 -2
package/package.json
CHANGED
|
@@ -998,6 +998,17 @@ class ClaudeStreamSession {
|
|
|
998
998
|
}
|
|
999
999
|
}
|
|
1000
1000
|
|
|
1001
|
+
/** このセッションの常駐 query 内で MCP サーバーを再接続する (動作中チャットへ波及)。
|
|
1002
|
+
* 常駐 query が無い (per-message セッション) 場合は false。per-message は毎ターン
|
|
1003
|
+
* 新プロセスで MCP を張り直すため、次メッセージで自動的に再接続される。
|
|
1004
|
+
* @returns {Promise<boolean>} 再接続を発行できたら true */
|
|
1005
|
+
async reconnectMcp(serverName) {
|
|
1006
|
+
const q = this._residentQuery
|
|
1007
|
+
if (!q || typeof q.reconnectMcpServer !== "function") return false
|
|
1008
|
+
await q.reconnectMcpServer(serverName)
|
|
1009
|
+
return true
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1001
1012
|
/**
|
|
1002
1013
|
* graceful detach: 実行中ターンがあれば中断せず完走を待つ (完走後に finally で
|
|
1003
1014
|
* 自動クローズ + onReap)。アイドルなら即クローズする。
|
|
@@ -1360,7 +1371,7 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
1360
1371
|
return await q.mcpServerStatus()
|
|
1361
1372
|
}
|
|
1362
1373
|
|
|
1363
|
-
/** 指定 MCP
|
|
1374
|
+
/** 指定 MCP サーバーを control 専用 query 内で再接続し、最新状態一覧を返す。 */
|
|
1364
1375
|
async reconnectMcp(serverName) {
|
|
1365
1376
|
const q = await this._ensureMcpControlQuery()
|
|
1366
1377
|
this._armMcpControlIdle()
|
|
@@ -1368,6 +1379,32 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
1368
1379
|
return await q.mcpServerStatus()
|
|
1369
1380
|
}
|
|
1370
1381
|
|
|
1382
|
+
/** 指定 MCP サーバーを「動作中の全 resident セッション」+ control query で再接続する。
|
|
1383
|
+
* MCP 接続はプロセス単位のため、control query だけ張り直しても動作中チャットには
|
|
1384
|
+
* 波及しない。そこで全 resident セッションの常駐 query にも reconnect を発行し、
|
|
1385
|
+
* 会話中の MCP 接続を実際に張り直す。reconnect は冪等なので多重発行は無害。
|
|
1386
|
+
* @returns {Promise<{servers: object[], targeted: number}>}
|
|
1387
|
+
* servers: control query の最新状態 (popover 表示用) / targeted: 波及したセッション数 */
|
|
1388
|
+
async reconnectMcpAllSessions(serverName) {
|
|
1389
|
+
const seen = new Set()
|
|
1390
|
+
let targeted = 0
|
|
1391
|
+
for (const session of this.sessions.values()) {
|
|
1392
|
+
if (seen.has(session)) continue
|
|
1393
|
+
seen.add(session)
|
|
1394
|
+
try {
|
|
1395
|
+
if (await session.reconnectMcp(serverName)) targeted += 1
|
|
1396
|
+
} catch (err) {
|
|
1397
|
+
this.logger?.warn(
|
|
1398
|
+
{ err: err?.message, server: serverName },
|
|
1399
|
+
"session mcp reconnect failed",
|
|
1400
|
+
)
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
// control query も張り直して popover 表示用の最新状態を返す。
|
|
1404
|
+
const servers = await this.reconnectMcp(serverName)
|
|
1405
|
+
return { servers, targeted }
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1371
1408
|
/** 指定 MCP サーバーの OAuth 認証を開始する。認可 URL を含む応答を返す。 */
|
|
1372
1409
|
async mcpAuthenticate(serverName, redirectUri) {
|
|
1373
1410
|
const q = await this._ensureMcpControlQuery()
|
package/src/main.mjs
CHANGED
|
@@ -990,11 +990,14 @@ async function dispatch(msg, ctx) {
|
|
|
990
990
|
case "claude.mcp.status":
|
|
991
991
|
result = { servers: await ctx.claudeBridge.mcpStatus() }
|
|
992
992
|
break
|
|
993
|
-
case "claude.mcp.reconnect":
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
993
|
+
case "claude.mcp.reconnect": {
|
|
994
|
+
// 動作中の全 resident セッション + control query を再接続 (会話中の MCP に波及)。
|
|
995
|
+
const r = await ctx.claudeBridge.reconnectMcpAllSessions(
|
|
996
|
+
msg.server_name,
|
|
997
|
+
)
|
|
998
|
+
result = { servers: r.servers, targeted: r.targeted }
|
|
997
999
|
break
|
|
1000
|
+
}
|
|
998
1001
|
case "claude.mcp.authenticate":
|
|
999
1002
|
result = {
|
|
1000
1003
|
auth: await ctx.claudeBridge.mcpAuthenticate(
|
|
@@ -1003,14 +1006,20 @@ async function dispatch(msg, ctx) {
|
|
|
1003
1006
|
),
|
|
1004
1007
|
}
|
|
1005
1008
|
break
|
|
1006
|
-
case "claude.mcp.callback":
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1009
|
+
case "claude.mcp.callback": {
|
|
1010
|
+
const auth = await ctx.claudeBridge.mcpSubmitOAuthCallback(
|
|
1011
|
+
msg.server_name,
|
|
1012
|
+
msg.callback_url,
|
|
1013
|
+
)
|
|
1014
|
+
// 再認証で得たトークンはディスクに永続するが、動作中セッションは別プロセス
|
|
1015
|
+
// のため自動では拾わない。全 resident セッション + control query を再接続し、
|
|
1016
|
+
// 会話中もすぐ新トークンで使えるようにする。
|
|
1017
|
+
const r = await ctx.claudeBridge.reconnectMcpAllSessions(
|
|
1018
|
+
msg.server_name,
|
|
1019
|
+
)
|
|
1020
|
+
result = { auth, servers: r.servers, targeted: r.targeted }
|
|
1013
1021
|
break
|
|
1022
|
+
}
|
|
1014
1023
|
case "claude.mcp.clearauth":
|
|
1015
1024
|
result = {
|
|
1016
1025
|
cleared: await ctx.claudeBridge.mcpClearAuth(msg.server_name),
|
package/src/ws-client.mjs
CHANGED
|
@@ -23,6 +23,12 @@ const MIN_BACKOFF_MS = 1_000
|
|
|
23
23
|
const MAX_BACKOFF_MS = 30_000
|
|
24
24
|
const BUNDLE_WATCH_DEBOUNCE_MS = 500
|
|
25
25
|
|
|
26
|
+
// 接続が安定したとみなすまでの猶予。open 直後に即 backoff をリセットすると、
|
|
27
|
+
// open→即 close のフラッピング時に毎回 backoff が最小 (1s) へ戻り、1〜2 秒間隔の
|
|
28
|
+
// 再接続ストロボに陥る (CF/backend 側の瞬断時に hub-agent↔backend WS で観測)。
|
|
29
|
+
// この時間だけ接続を維持できて初めて backoff をリセットする。
|
|
30
|
+
const STABLE_CONNECTION_MS = 10_000
|
|
31
|
+
|
|
26
32
|
// CF / origin が 5xx を返した直後の再接続は短い backoff だと 5xx キャッシュに
|
|
27
33
|
// 当たって連発失敗するため、最低 5s 待つ。30s リトライまで段階的に伸びる。
|
|
28
34
|
const MIN_BACKOFF_AFTER_5XX_MS = 5_000
|
|
@@ -76,6 +82,9 @@ export class WsClient extends EventEmitter {
|
|
|
76
82
|
this.ptyOutboundBuffer = []
|
|
77
83
|
// 直前の close 原因が CF/origin の 5xx か。次回 backoff の下限を引き上げる。
|
|
78
84
|
this.lastCloseWas5xx = false
|
|
85
|
+
// 接続が STABLE_CONNECTION_MS 維持できたら backoff/5xx フラグをリセットする
|
|
86
|
+
// タイマー。close (_clearStableReset) でキャンセルされる。
|
|
87
|
+
this.stableResetTimer = null
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
/** WSS 接続を開始する。`stop()` まで自動で reconnect 続行。 */
|
|
@@ -104,6 +113,7 @@ export class WsClient extends EventEmitter {
|
|
|
104
113
|
})
|
|
105
114
|
|
|
106
115
|
ws.on("close", (code, reason) => {
|
|
116
|
+
this._clearStableReset()
|
|
107
117
|
this._stopHeartbeat()
|
|
108
118
|
this._stopBundleWatcher()
|
|
109
119
|
this.logger?.info({ code, reason: reason?.toString() }, "ws close")
|
|
@@ -136,8 +146,11 @@ export class WsClient extends EventEmitter {
|
|
|
136
146
|
* のクロージャだと spy しづらい)。
|
|
137
147
|
*/
|
|
138
148
|
_onOpen() {
|
|
139
|
-
|
|
140
|
-
|
|
149
|
+
// backoff の即時リセットはしない。接続が STABLE_CONNECTION_MS 維持できてから
|
|
150
|
+
// _armStableReset() でリセットする (open→即 close フラッピング時の再接続
|
|
151
|
+
// ストロボ対策)。すぐ切れた接続では backoff がリセットされず指数バックオフが
|
|
152
|
+
// 効き続けるため、1〜2 秒間隔の再接続ループに陥らない。
|
|
153
|
+
this._armStableReset()
|
|
141
154
|
this.logger?.info("ws open")
|
|
142
155
|
// 切断中に積んだ pty.data を flush。hello より先に送ると backend で stream
|
|
143
156
|
// 未登録のため unknown_stream error になる可能性があるが、hello の応答待ち
|
|
@@ -232,6 +245,7 @@ export class WsClient extends EventEmitter {
|
|
|
232
245
|
/** Reconnect を止めて切断する。 */
|
|
233
246
|
stop() {
|
|
234
247
|
this.stopped = true
|
|
248
|
+
this._clearStableReset()
|
|
235
249
|
this._stopHeartbeat()
|
|
236
250
|
this._stopBundleWatcher()
|
|
237
251
|
if (this.reconnectTimer) {
|
|
@@ -428,4 +442,29 @@ export class WsClient extends EventEmitter {
|
|
|
428
442
|
}, delay)
|
|
429
443
|
this.reconnectTimer.unref?.()
|
|
430
444
|
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* 接続が STABLE_CONNECTION_MS 維持できたら backoff と 5xx フラグをリセットする
|
|
448
|
+
* タイマーを張る (open 直後に呼ぶ)。close で _clearStableReset によりキャンセル
|
|
449
|
+
* されるため、安定しなかった (= すぐ切れた) 接続では backoff がリセットされず
|
|
450
|
+
* 指数バックオフが効き続け、再接続ストロボを防ぐ。
|
|
451
|
+
*/
|
|
452
|
+
_armStableReset() {
|
|
453
|
+
this._clearStableReset()
|
|
454
|
+
this.stableResetTimer = setTimeout(() => {
|
|
455
|
+
this.stableResetTimer = null
|
|
456
|
+
this.backoff = MIN_BACKOFF_MS
|
|
457
|
+
this.lastCloseWas5xx = false
|
|
458
|
+
this.logger?.debug("ws connection stable, backoff reset")
|
|
459
|
+
}, STABLE_CONNECTION_MS)
|
|
460
|
+
this.stableResetTimer.unref?.()
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** 安定リセットタイマーをキャンセルする (close / stop 時)。 */
|
|
464
|
+
_clearStableReset() {
|
|
465
|
+
if (this.stableResetTimer) {
|
|
466
|
+
clearTimeout(this.stableResetTimer)
|
|
467
|
+
this.stableResetTimer = null
|
|
468
|
+
}
|
|
469
|
+
}
|
|
431
470
|
}
|