@cocorograph/hub-agent 0.7.92 → 0.7.94

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/main.mjs +73 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.7.92",
3
+ "version": "0.7.94",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
package/src/main.mjs CHANGED
@@ -3076,6 +3076,50 @@ export function handleClaudeChatInput(msg, ctx) {
3076
3076
  })
3077
3077
  }
3078
3078
 
3079
+ /**
3080
+ * claude.attach の ready 直後にpushする履歴を組み立てる。
3081
+ * Browserからの claude.history.request 往復を省くため、resume時だけ先回りして読む。
3082
+ * 失敗しても空のhistory.responseへ縮退し、attach自体を失敗させない。
3083
+ */
3084
+ export async function loadClaudeAttachHistory({
3085
+ cwd,
3086
+ sessionId,
3087
+ logger,
3088
+ getProjectsRootFn = getActiveProjectsRoot,
3089
+ fetchHistoryFn = fetchSessionHistory,
3090
+ }) {
3091
+ if (!cwd || !sessionId) return null
3092
+ try {
3093
+ const projectsRoot = await getProjectsRootFn()
3094
+ const result = await fetchHistoryFn({
3095
+ cwd,
3096
+ session_id: sessionId,
3097
+ maxLines: 500,
3098
+ projectsRoot,
3099
+ logger,
3100
+ })
3101
+ return {
3102
+ type: "claude.history.response",
3103
+ session_id: sessionId,
3104
+ cwd,
3105
+ events: result.events || [],
3106
+ total_lines: result.total_lines || 0,
3107
+ truncated: result.truncated === true,
3108
+ error: result.error,
3109
+ }
3110
+ } catch (err) {
3111
+ return {
3112
+ type: "claude.history.response",
3113
+ session_id: sessionId,
3114
+ cwd,
3115
+ events: [],
3116
+ total_lines: 0,
3117
+ truncated: false,
3118
+ error: err?.message || String(err),
3119
+ }
3120
+ }
3121
+ }
3122
+
3079
3123
  /**
3080
3124
  * SDK チャット配達保証 (S2, 2026-07-05): `codex.input` を処理し、browser が付与した
3081
3125
  * `input_id` があれば `codex.input.ack` / `codex.input.nack` を返す (claude.input と
@@ -3494,6 +3538,16 @@ async function dispatch(msg, ctx) {
3494
3538
  // model / permissionMode は agent.json の値も使えるよう fallback。
3495
3539
  const cwdArg = msg.cwd || ctx.config?.claude_cwd || undefined
3496
3540
  const resumeSessionId = msg.resume_session_id || null
3541
+ // attachは同期処理なので、履歴I/Oを先に開始してreadyまでの待ちを重ねる。
3542
+ // 新Browserはhistory_pushedを見て別requestを省略し、旧Browserは従来どおり
3543
+ // requestしてもhydrateのuuid dedupで安全に収束する。
3544
+ const historyPromise = resumeSessionId
3545
+ ? loadClaudeAttachHistory({
3546
+ cwd: cwdArg || process.env.HOME || process.cwd(),
3547
+ sessionId: resumeSessionId,
3548
+ logger: ctx.logger,
3549
+ })
3550
+ : Promise.resolve(null)
3497
3551
  const info = ctx.claudeBridge.attach({
3498
3552
  stream_id,
3499
3553
  cwd: cwdArg,
@@ -3517,29 +3571,44 @@ async function dispatch(msg, ctx) {
3517
3571
  type: "claude.ready",
3518
3572
  stream_id,
3519
3573
  resuming: info.resuming,
3574
+ history_pushed: resumeSessionId !== null,
3520
3575
  // id 空間統一 (2026-07-08): この agent は browser の input_id をキュー id として
3521
3576
  // 採用し、楽観送信待ちアイテムの確認前キャンセルに対応することを通知する capability。
3522
3577
  // 旧 agent にはこのフラグが無く、browser は楽観アイテムの×を出さない (ゴースト
3523
3578
  // 実行防止のフォールバック)。
3524
3579
  supports_queue_input_id: true,
3525
3580
  })
3581
+ // dispatch全体はWSメッセージ順を守る直列キュー上にある。ここで履歴I/Oをawaitすると
3582
+ // 同一接続のpermission/input等まで停止するため、readyの同期送信後は非同期でpushする。
3583
+ void historyPromise
3584
+ .then((pushedHistory) => {
3585
+ if (pushedHistory) ctx.client.send({ ...pushedHistory, stream_id })
3586
+ })
3587
+ .catch((err) => {
3588
+ ctx.logger?.warn?.(
3589
+ { err: err?.message || String(err), stream_id },
3590
+ "failed to push Claude attach history",
3591
+ )
3592
+ })
3526
3593
  // S1 (2026-07): セッション状態の identity 正本化。TUI の claude.tui.bind
3527
3594
  // (main.mjs 内 clearChatSignal 呼び出し箇所参照) と対称に、SDK チャットの
3528
3595
  // attach 時点でも前セッションのチャット信号を引き継がない。
3529
3596
  // - fresh (resume 無し): 新規セッションで session_id はまだ確定しない
3530
- // (SDK が最初の assistant イベントで通知する) ため null。前の信号を破棄し
3531
- // ゼロ値を即時 push してドーナツ/ドットを即座にリセットする (症状④)。
3597
+ // (SDK が最初の assistant イベントで通知する)。ただし null を送ると frontend
3598
+ // は「同一会話で一時的に0%になった」と判断して旧値を保持するため、stream_id
3599
+ // 由来の一意な仮IDを先に送る。実 session_id が来た時点で置換される。
3532
3600
  // - resume: bound session の jsonl から実 context% を解決し、cli_kind='claude' +
3533
3601
  // 実 session_id で identity を確定させて即時 push する (症状③)。
3534
3602
  const resolvedCwd = cwdArg || process.env.HOME || process.cwd()
3535
3603
  if (resolvedCwd) {
3536
3604
  if (!resumeSessionId) {
3605
+ const freshContextSessionId = `fresh:${stream_id}`
3537
3606
  clearChatSignal(resolvedCwd, "claude")
3538
3607
  recordChatActivity(resolvedCwd, "claude", {
3539
3608
  status: "waiting",
3540
3609
  contextPct: 0,
3541
3610
  inputPending: false,
3542
- sessionId: null,
3611
+ sessionId: freshContextSessionId,
3543
3612
  })
3544
3613
  pushImmediateChatState({
3545
3614
  client: ctx.client,
@@ -3547,7 +3616,7 @@ async function dispatch(msg, ctx) {
3547
3616
  logger: ctx.logger,
3548
3617
  cwd: resolvedCwd,
3549
3618
  cliKind: "claude",
3550
- sessionId: null,
3619
+ sessionId: freshContextSessionId,
3551
3620
  contextPct: 0,
3552
3621
  }).catch(() => {})
3553
3622
  } else {