@cocorograph/hub-agent 0.6.62 → 0.6.64

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.6.62",
3
+ "version": "0.6.64",
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
@@ -69,6 +69,7 @@ import {
69
69
  removeWorktree as removeWorktreeDir,
70
70
  resumeWithMessage,
71
71
  setTmuxGlobalEnv,
72
+ setTuiEffort,
72
73
  setTuiModel,
73
74
  } from "./tmux.mjs"
74
75
  import { TuiPermissionBridge } from "./tui-permission-bridge.mjs"
@@ -572,7 +573,7 @@ export async function startDaemon({ version, ptyModule, claudeSdk } = {}) {
572
573
  const tuiPermissionBridge = new TuiPermissionBridge({ logger })
573
574
  tuiPermissionBridge.on(
574
575
  "permission",
575
- ({ request_id, session_id, cwd, tool_name, input }) => {
576
+ ({ request_id, session_id, cwd, tool_name, input, context_text }) => {
576
577
  if (cwd) {
577
578
  try {
578
579
  recordChatActivity(cwd, { inputPending: true })
@@ -588,6 +589,8 @@ export async function startDaemon({ version, ptyModule, claudeSdk } = {}) {
588
589
  request_id,
589
590
  tool_name,
590
591
  input,
592
+ // 質問/承認カードの直前アシスタント説明 (フック由来。browser がカード上部に表示)。
593
+ context_text: context_text ?? null,
591
594
  })
592
595
  },
593
596
  )
@@ -1419,6 +1422,39 @@ async function dispatch(msg, ctx) {
1419
1422
  })()
1420
1423
  return
1421
1424
  }
1425
+ case "claude.tui.setEffort": {
1426
+ // effort バッジ選択 → 対話 claude TUI へ `/effort <level>` を送って reasoning effort を
1427
+ // 切り替える。setModel と同設計: agent が実キーを送出 → 全ブラウザへ claude.tui.effort を
1428
+ // broadcast し、実際に動いているターミナルの effort を正本として全端末に同期する。
1429
+ // effort は jsonl に記録されないため、この即時 broadcast が唯一の表示根拠になる。
1430
+ const cwd = typeof msg.cwd === "string" ? msg.cwd : ""
1431
+ const sessionName =
1432
+ typeof msg.session_name === "string" ? msg.session_name : ""
1433
+ if (!sessionName) return
1434
+ // effort="" は「auto」= `/effort auto`。frontend へはそのまま空で返し、バッジは auto 表示。
1435
+ const effort = typeof msg.effort === "string" ? msg.effort : ""
1436
+ ;(async () => {
1437
+ try {
1438
+ await setTuiEffort(sessionName, effort || "auto", { logger })
1439
+ ctx.client.send({
1440
+ type: "claude.tui.effort",
1441
+ cwd: cwd || undefined,
1442
+ session_name: sessionName,
1443
+ effort,
1444
+ })
1445
+ logger.info(
1446
+ { session: sessionName, cwd, effort: effort || "(auto)" },
1447
+ "tui effort switched → notified browser",
1448
+ )
1449
+ } catch (err) {
1450
+ logger.warn(
1451
+ { err: err?.message, session: sessionName },
1452
+ "claude.tui.setEffort failed",
1453
+ )
1454
+ }
1455
+ })()
1456
+ return
1457
+ }
1422
1458
  case "claude.tui.probePermission": {
1423
1459
  // 読み取り専用の権限モード問い合わせ (cold-load seed)。キーは送らず、ペインを
1424
1460
  // 読んで現在の実モードを claude.tui.permission として broadcast するだけ。
@@ -1501,6 +1537,8 @@ async function dispatch(msg, ctx) {
1501
1537
  request_id: p.request_id,
1502
1538
  tool_name: p.tool_name,
1503
1539
  input: p.input,
1540
+ // 再 hydrate でも直前説明を保つ (listPending が payload ごと保持している)。
1541
+ context_text: p.context_text ?? null,
1504
1542
  })
1505
1543
  }
1506
1544
  if (pend.length) {
package/src/tmux.mjs CHANGED
@@ -934,6 +934,59 @@ export async function setTuiModel(name, modelArg, opts = {}) {
934
934
  }
935
935
  }
936
936
 
937
+ /**
938
+ * 対話 claude TUI に `/effort <level>` を送って reasoning effort を切り替える。
939
+ *
940
+ * cockpit の effort バッジ選択 (claude.tui.setEffort) の書込側。モデル切替 (setTuiModel) と
941
+ * 同設計で、frontend は raw pty.data ではなく claude.tui.setEffort を送り、agent 側で本関数を
942
+ * 実行 → 全ブラウザへ claude.tui.effort を broadcast して全端末を実 effort に揃える。
943
+ *
944
+ * `/effort` は引数 (low/medium/high/xhigh/max/auto) を受理し即時反映する (確認プロンプトを
945
+ * 挟まない)。effort 非対応レベルを与えても CLI がモデルの対応上限へ自動フォールバックする。
946
+ * `auto` はモデル既定へリセット。copy-mode に入っているとキーが奪われるので先に抜ける。
947
+ * ベストエフォート。
948
+ *
949
+ * @param {string} name tmux セッション名
950
+ * @param {string} effortArg `/effort` 引数 (low/medium/high/xhigh/max または "auto")
951
+ * @param {{logger?:object,tmuxBin?:string}} [opts]
952
+ * @returns {Promise<{ok:boolean, error?:string}>}
953
+ */
954
+ export async function setTuiEffort(name, effortArg, opts = {}) {
955
+ const bin = tmuxBin(opts)
956
+ const arg = String(effortArg || "auto").replace(/[\r\n]+/g, " ").trim()
957
+ if (!arg) return { ok: false, error: "empty effort arg" }
958
+ try {
959
+ // copy-mode 等に入っているとキーが奪われるので、入っている時だけ抜ける。
960
+ try {
961
+ const { stdout } = await execFileP(bin, [
962
+ "display-message",
963
+ "-p",
964
+ "-t",
965
+ `${name}:`,
966
+ "-F",
967
+ "#{pane_in_mode}",
968
+ ])
969
+ if (stdout.trim() === "1") {
970
+ await execFileP(bin, ["send-keys", "-t", name, "-X", "cancel"])
971
+ }
972
+ } catch {
973
+ // pane_in_mode 取得失敗はベストエフォートで無視。
974
+ }
975
+ // `/effort <level>` をリテラルで送る (-l でキー名解釈・スラッシュ補完の暴発を避ける)。
976
+ await execFileP(bin, ["send-keys", "-t", name, "-l", `/effort ${arg}`])
977
+ await _delay(120)
978
+ // Enter で本文確定 (send-keys の離散イベントなので paste 吸収は起きにくい)。
979
+ await execFileP(bin, ["send-keys", "-t", name, "Enter"])
980
+ return { ok: true }
981
+ } catch (err) {
982
+ opts.logger?.warn(
983
+ { session: name, effort: arg, err: err?.message },
984
+ "setTuiEffort failed",
985
+ )
986
+ return { ok: false, error: err?.message || String(err) }
987
+ }
988
+ }
989
+
937
990
  /**
938
991
  * 中断キャンセル後の入力欄復旧 (claude.tui.recoverInput / agent >= 0.6.57)。
939
992
  *
@@ -111,6 +111,9 @@ export class TuiPermissionBridge extends EventEmitter {
111
111
  cwd: body.cwd ?? null,
112
112
  tool_name: body.tool_name ?? "",
113
113
  input: body.tool_input ?? {},
114
+ // 質問/承認カードの直前アシスタント説明 (フックが transcript から抽出して同梱)。
115
+ // browser はこれをカード上部に表示し、jsonl tail の到着レースに依らず文脈を読める。
116
+ context_text: body.context_text ?? null,
114
117
  }
115
118
  // payload も保持する (セッション切替でビュー再マウント時の re-hydrate / listPending 用)。
116
119
  this._pending.set(request_id, { payload, at: Date.now() })