@cocorograph/hub-agent 0.7.104 → 0.7.105
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/codex-stream-bridge.mjs +17 -0
- package/src/main.mjs +41 -0
package/package.json
CHANGED
|
@@ -334,6 +334,10 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
334
334
|
/** @type {Map<string, object>} stream_id -> session record */
|
|
335
335
|
this.sessions = new Map()
|
|
336
336
|
this._accountRateLimitsByProfile = new Map()
|
|
337
|
+
// Phase 3 (Read-plane): listThreads の single-flight。key(`cwd::limit`) -> in-flight Promise。
|
|
338
|
+
// codex.sessions.request を直列キューから外したため、同一 cwd への同時リクエストが
|
|
339
|
+
// app-server の同時 spawn (storm) を起こさないよう 1 本に畳む。
|
|
340
|
+
this._listThreadsInFlight = new Map()
|
|
337
341
|
}
|
|
338
342
|
|
|
339
343
|
/**
|
|
@@ -762,6 +766,19 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
762
766
|
* @returns {Promise<Array<{thread_id: string, cwd: string, preview: string, updated_at_ms: number}>>}
|
|
763
767
|
*/
|
|
764
768
|
async listThreads({ cwd, limit } = {}) {
|
|
769
|
+
// Phase 3 (Read-plane): single-flight。同一 (cwd, limit) への同時リクエストを 1 本の
|
|
770
|
+
// in-flight Promise に畳み、app-server の同時 spawn (storm) を防ぐ。異なる cwd は個別に走る。
|
|
771
|
+
const key = `${cwd || ""}::${limit ?? ""}`
|
|
772
|
+
const existing = this._listThreadsInFlight.get(key)
|
|
773
|
+
if (existing) return existing
|
|
774
|
+
const p = this._doListThreads({ cwd, limit }).finally(() => {
|
|
775
|
+
this._listThreadsInFlight.delete(key)
|
|
776
|
+
})
|
|
777
|
+
this._listThreadsInFlight.set(key, p)
|
|
778
|
+
return p
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
async _doListThreads({ cwd, limit } = {}) {
|
|
765
782
|
const client = this._clientFactory({ cwd, codexCmd: this.codexCmd, logger: this.logger })
|
|
766
783
|
client.start()
|
|
767
784
|
try {
|
package/src/main.mjs
CHANGED
|
@@ -342,6 +342,33 @@ export function isFastPathMessage(type) {
|
|
|
342
342
|
)
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Phase 3 (Read-plane 2026-07-24): タイムライン/セッション一覧の「読み取り専用 RPC」を判定する。
|
|
347
|
+
* これらは disk (jsonl) / codex rollout を読んで自前の response を送るだけで pane・入力・bridge の
|
|
348
|
+
* 状態を一切変更しない (ハンドラ本体で確認済み: claude.history.request=fetchSessionHistory /
|
|
349
|
+
* claude.sessions.request=listSessions / codex.sessions.request=listThreads、いずれも純粋読み取り)。
|
|
350
|
+
* メッセージ自身が cwd/session_id/stream_id を持ち、frontend も stream_id/cwd 照合で stale 応答を
|
|
351
|
+
* 捨てるため、WS 受信順に対する順序依存が無い。
|
|
352
|
+
*
|
|
353
|
+
* これを直列 dispatchChain から外す理由 (view/spawn 分離の核): 直列キューは「WS 受信順 = pane 反映順」
|
|
354
|
+
* を守るためのものだが、その代償として前方の重い処理 (codex.sessions.request/codex.attach の
|
|
355
|
+
* app-server spawn = 数秒、claude.sessions.request の全ファイル読み) が後続の claude.attach を
|
|
356
|
+
* head-of-line blocking し、attach→ready を数十秒遅延させていた (本番計測 cold p95 40〜54s)。
|
|
357
|
+
* claude/codex が同一 dispatchChain を共有するため codex の spawn が claude の切替まで巻き込む。
|
|
358
|
+
* 読み取り RPC を並行レーンへ逃がすことで、タイムライン閲覧が live 接続 (spawn) の完了を待たなくなる。
|
|
359
|
+
*
|
|
360
|
+
* ⚠️ codex.sessions.request は listThreads が app-server を spawn するため、off-queue 化の副作用で
|
|
361
|
+
* 同一 cwd への同時リクエストが spawn storm を起こしうる。CodexStreamBridge.listThreads 側に
|
|
362
|
+
* single-flight ガードを併設して同時 spawn を 1 本に畳む (B で rollout disk 走査へ置換すれば spawn 自体が消える)。
|
|
363
|
+
*/
|
|
364
|
+
export function isReadOnlyRpc(type) {
|
|
365
|
+
return (
|
|
366
|
+
type === "claude.history.request" ||
|
|
367
|
+
type === "claude.sessions.request" ||
|
|
368
|
+
type === "codex.sessions.request"
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
|
|
345
372
|
/**
|
|
346
373
|
* active プロファイルの configDir を process.env.CLAUDE_CONFIG_DIR へ反映する。
|
|
347
374
|
* 既定 (`~/.claude`) のときは変数を消して従来挙動 (Claude Code の既定パス) に戻す。
|
|
@@ -1122,6 +1149,20 @@ export async function startDaemon({ version, ptyModule, claudeSdk } = {}) {
|
|
|
1122
1149
|
})
|
|
1123
1150
|
return
|
|
1124
1151
|
}
|
|
1152
|
+
// Phase 3 (Read-plane): 読み取り専用 RPC (history/sessions) も直列キューから外して並行処理する。
|
|
1153
|
+
// 直列キュー前方の重い処理 (codex app-server spawn 等) が claude.attach→ready を HoL blocking
|
|
1154
|
+
// するのを防ぐ (view/spawn 分離)。純粋読み取りで順序依存が無いことは isReadOnlyRpc の説明参照。
|
|
1155
|
+
if (isReadOnlyRpc(msg?.type)) {
|
|
1156
|
+
Promise.resolve(
|
|
1157
|
+
dispatch(msg, { ...ctx, client, ptyBridge, claudeBridge, codexBridge, codexMcpControl, uploadManager }),
|
|
1158
|
+
).catch((err) => {
|
|
1159
|
+
logger.error(
|
|
1160
|
+
{ err: err.message, type: msg?.type },
|
|
1161
|
+
"dispatch threw (bypassed read-only RPC lane)",
|
|
1162
|
+
)
|
|
1163
|
+
})
|
|
1164
|
+
return
|
|
1165
|
+
}
|
|
1125
1166
|
dispatchChain = dispatchChain
|
|
1126
1167
|
.then(() => dispatch(msg, { ...ctx, client, ptyBridge, claudeBridge, codexBridge, codexMcpControl, uploadManager }))
|
|
1127
1168
|
.catch((err) => {
|