@cocorograph/hub-agent 0.6.16 → 0.6.17

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.16",
3
+ "version": "0.6.17",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -268,14 +268,89 @@ class ClaudeStreamSession {
268
268
  /** 再アタッチ: 走行中(または生存中)セッションに新しい stream_id を紐付け直し、
269
269
  * idle 撤去タイマーを止める。以降のターンイベントはこの stream_id 経由で新しい
270
270
  * browser 接続へライブに流れる (= 通常の生成中表示と同じ)。再アタッチ前の確定分は
271
- * browser 側の jsonl hydrate (history.request) で復元するため、ここでは replay しない。 */
272
- reattach(stream_id) {
271
+ * browser 側の jsonl hydrate (history.request) で復元するため、ここでは replay しない。
272
+ *
273
+ * 改修5 (2026-05-29): モデル/権限/拡張思考のターン切替。再アタッチ時に opts で
274
+ * 新しい値が渡され、現在値と異なれば applyRuntimeOptions で反映する。これにより
275
+ * 入力欄下バッジの変更が「同一セッション(常駐 query)を維持したまま次ターンから」
276
+ * 効くようになる。従来は reattach が stream_id だけ差し替えていたため、起動済み
277
+ * query の model/permission/thinking は初期値のまま変わらなかった (バッジが効かない
278
+ * 不具合の原因)。 */
279
+ reattach(stream_id, opts = undefined) {
273
280
  this.stream_id = stream_id
274
281
  this._detached = false
275
282
  if (this._idleTimer) {
276
283
  clearTimeout(this._idleTimer)
277
284
  this._idleTimer = null
278
285
  }
286
+ if (opts) this.applyRuntimeOptions(opts)
287
+ }
288
+
289
+ /** 改修5: モデル/権限/拡張思考をランタイムに切り替える。
290
+ *
291
+ * - 保持フィールド (this.model/permissionMode/maxThinkingTokens) を更新する。これは
292
+ * 常駐 query が異常終了して resume 再起動する際 (_runResidentQuery) に最新値で
293
+ * 再 spawn させるため、および per-message セッションが次ターンの options に反映する
294
+ * ため。
295
+ * - 起動済みの常駐 query があれば SDK の制御メソッド (setModel / setPermissionMode /
296
+ * setMaxThinkingTokens) を呼び、プロセス再起動なしで次ターンから即反映する
297
+ * (公式 streaming input mode のランタイム制御。stdin に control_request を流す)。
298
+ *
299
+ * 値が undefined のキーは「変更なし」として無視する (バッジ未送出時に既存値を消さない)。
300
+ * model に空文字/null が来たら setModel(undefined) でデフォルトへ戻す。
301
+ * maxThinkingTokens に 0/null が来たら setMaxThinkingTokens(null) でオフにする。 */
302
+ applyRuntimeOptions({ model, permissionMode, maxThinkingTokens } = {}) {
303
+ const q = this._residentQuery
304
+ // モデル
305
+ if (model !== undefined) {
306
+ const next = model || null
307
+ if (next !== this.model) {
308
+ this.model = next
309
+ if (q && typeof q.setModel === "function") {
310
+ q.setModel(next || undefined).catch((err) =>
311
+ this.logger?.warn(
312
+ { err: err?.message, stream_id: this.stream_id },
313
+ "setModel failed",
314
+ ),
315
+ )
316
+ }
317
+ }
318
+ }
319
+ // 権限モード
320
+ if (permissionMode !== undefined) {
321
+ const next = permissionMode || null
322
+ if (next !== this.permissionMode) {
323
+ this.permissionMode = next
324
+ // setPermissionMode は有効な mode を要求する。null/空 (=未指定へ戻す) は
325
+ // SDK 側に「解除」API が無いため、保持値の更新のみ (次回 spawn で既定に従う)。
326
+ if (next && q && typeof q.setPermissionMode === "function") {
327
+ q.setPermissionMode(next).catch((err) =>
328
+ this.logger?.warn(
329
+ { err: err?.message, stream_id: this.stream_id },
330
+ "setPermissionMode failed",
331
+ ),
332
+ )
333
+ }
334
+ }
335
+ }
336
+ // 拡張思考予算
337
+ if (maxThinkingTokens !== undefined) {
338
+ const next =
339
+ typeof maxThinkingTokens === "number" && maxThinkingTokens > 0
340
+ ? maxThinkingTokens
341
+ : null
342
+ if (next !== this.maxThinkingTokens) {
343
+ this.maxThinkingTokens = next
344
+ if (q && typeof q.setMaxThinkingTokens === "function") {
345
+ q.setMaxThinkingTokens(next).catch((err) =>
346
+ this.logger?.warn(
347
+ { err: err?.message, stream_id: this.stream_id },
348
+ "setMaxThinkingTokens failed",
349
+ ),
350
+ )
351
+ }
352
+ }
353
+ }
279
354
  }
280
355
 
281
356
  /** soft detach: browser 切断時にターンを中断せずセッションを生かしたまま detached に
@@ -740,10 +815,25 @@ export class ClaudeStreamBridge extends EventEmitter {
740
815
  const live = this._liveBySession.get(resumeSessionId)
741
816
  if (live && !live._closed) {
742
817
  this.sessions.delete(live.stream_id)
743
- live.reattach(stream_id)
818
+ // 改修5: 再アタッチ時に model/permission/maxThinkingTokens を引き継ぎ反映する。
819
+ // browser はバッジ変更時に新しい値を載せた claude.attach を同一 resume で送る
820
+ // ため、ここで適用すると常駐 query を維持したまま次ターンから切り替わる。
821
+ live.reattach(stream_id, {
822
+ model,
823
+ permissionMode,
824
+ maxThinkingTokens:
825
+ typeof maxThinkingTokens === "number" ? maxThinkingTokens : null,
826
+ })
744
827
  this.sessions.set(stream_id, live)
745
828
  this.logger?.info(
746
- { stream_id, resume: resumeSessionId, busy: live._busy },
829
+ {
830
+ stream_id,
831
+ resume: resumeSessionId,
832
+ busy: live._busy,
833
+ model: live.model,
834
+ permissionMode: live.permissionMode,
835
+ maxThinkingTokens: live.maxThinkingTokens,
836
+ },
747
837
  "claude stream reattached to live session",
748
838
  )
749
839
  return { stream_id, resuming: true, reattached: true }