@cocorograph/hub-agent 0.6.27 → 0.6.28

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.27",
3
+ "version": "0.6.28",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -209,6 +209,11 @@ class ClaudeStreamSession {
209
209
  /** 改修3: 直近 browser へ通知した queue 署名 (件数 + id 列)。変化時のみ emit する。
210
210
  * 空キューの署名 ("0:") で初期化し、空→空の冗長 emit を抑止する。 */
211
211
  this._lastEmittedQueueSig = "0:"
212
+ /** ultracode (0.6.28): 常駐 query へ現在適用済みの ultracode 状態。ターン単位の
213
+ * ワンショット適用を applyFlagSettings で reconcile する際の差分判定に使う。
214
+ * query を (再)起動すると flag settings は既定に戻るため、_runResidentQuery 冒頭で
215
+ * false に戻す。 */
216
+ this._ultracodeCurrent = false
212
217
 
213
218
  /** @type {Map<string, {resolve: (decision: object) => void}>} permission 応答待ち */
214
219
  this._permissionResolvers = new Map()
@@ -445,11 +450,18 @@ class ClaudeStreamSession {
445
450
  * 既存ターン実行中 (busy) は破棄せず pending キューへ退避し、現ターン完了時に drain する
446
451
  * (改修3)。常駐 query 対象 (新規セッション) は InputQueue へ積む (改修2)。
447
452
  */
448
- async sendMessage(message) {
453
+ async sendMessage(message, opts = {}) {
449
454
  if (this._closed) return
450
455
  const prompt = extractPromptText(message)
451
456
  if (!prompt) return
452
457
 
458
+ // ultracode ワンショット (0.6.28): このターンのみ xhigh effort + 常時 dynamic-workflow
459
+ // オーケストレーションを有効化する。セッション既定としては持たず (トークン消費が
460
+ // 桁違いになるため)、ターン単位でトグルし、完了後は通常状態へ戻す。
461
+ // per-message では options.settings に乗せ、resident では applyFlagSettings で
462
+ // ターン前に ON / 次ターン前に OFF へ reconcile する (詳細は _reconcileResidentUltracode)。
463
+ const ultracode = opts.ultracode === true
464
+
453
465
  // 改修2+4: 常駐query対象セッション。
454
466
  if (this._residentEligible) {
455
467
  if (!this._inputQueue) this._inputQueue = new InputQueue()
@@ -458,7 +470,7 @@ class ClaudeStreamSession {
458
470
  // 次を push すると SDK streaming-input で割り込み扱いになり得るため (公式 interrupt 警告)。
459
471
  // pending は queue_state で UI (送信待ちチップ) に出す (per-message と同じ体験)。
460
472
  if (this._busy) {
461
- this._enqueuePending(prompt)
473
+ this._enqueuePending(prompt, ultracode)
462
474
  this.logger?.info(
463
475
  { stream_id: this.stream_id, queued: this._pendingMessages.length },
464
476
  "resident busy, message queued",
@@ -467,14 +479,18 @@ class ClaudeStreamSession {
467
479
  return
468
480
  }
469
481
  this._busy = true
470
- this._inputQueue.push(toSDKUserMessage(prompt))
471
482
  // 改修4 (A): 死亡ガード。常駐 query が未起動 or (エラー等で) 終了済み (_residentQuery=null)
472
483
  // なら (再)起動する。_runResidentQuery は起動時 options.resume=this.sessionId で文脈を
473
484
  // 復元するため、途中死からの復活でも過去コンテキストは失われない。
485
+ // ultracode のとき: query を先に起動 (空 InputQueue なので入力待ちでブロック) し、
486
+ // applyFlagSettings を await してから push することで、設定適用前にターンが
487
+ // 消費されるレースを防ぐ。
474
488
  if (!this._residentQuery) {
475
489
  this._residentStarted = true
476
490
  this._startResidentQuery()
477
491
  }
492
+ await this._reconcileResidentUltracode(ultracode)
493
+ this._inputQueue.push(toSDKUserMessage(prompt))
478
494
  return
479
495
  }
480
496
 
@@ -482,7 +498,7 @@ class ClaudeStreamSession {
482
498
  // 改修3: busy 中の送信は破棄せず pending キューへ退避し、現ターン完了時に drain する
483
499
  // (ターミナル流の「積む→待機→順次実行」)。常駐 query 化はしないので暴走リスクは増えない。
484
500
  if (this._busy) {
485
- this._enqueuePending(prompt)
501
+ this._enqueuePending(prompt, ultracode)
486
502
  this.logger?.info(
487
503
  { stream_id: this.stream_id, queued: this._pendingMessages.length },
488
504
  "claude busy, message queued",
@@ -490,12 +506,12 @@ class ClaudeStreamSession {
490
506
  this._emitQueueState()
491
507
  return
492
508
  }
493
- return this._runPerMessage(prompt)
509
+ return this._runPerMessage(prompt, { ultracode })
494
510
  }
495
511
 
496
512
  /** per-message 1 ターンを実行する (resume チェーン)。busy 中に届いた送信は sendMessage
497
513
  * が pending キューへ退避し、本メソッドの finally で drain する。 */
498
- async _runPerMessage(prompt) {
514
+ async _runPerMessage(prompt, opts = {}) {
499
515
  this._busy = true
500
516
  this._abortController = new AbortController()
501
517
  let aborted = false
@@ -512,6 +528,16 @@ class ClaudeStreamSession {
512
528
  if (this.maxTurns != null) options.maxTurns = this.maxTurns
513
529
  // 思考オプション (effort / adaptive thinking / 旧 budget) はモデルに応じて切替。
514
530
  this._applyThinkingOptions(options)
531
+ // ultracode ワンショット (0.6.28): per-message は 1 query = 1 ターンなので、
532
+ // このターンの options.settings (= --settings 相当 / flag settings 層) に乗せるだけで
533
+ // 自然に 1 ターン限定になる。次ターンは options を作り直すため通常状態に戻る。
534
+ if (opts.ultracode === true) {
535
+ options.settings = {
536
+ ...(options.settings || {}),
537
+ ultracode: true,
538
+ enableWorkflows: true,
539
+ }
540
+ }
515
541
  // 直前ターンまでの session_id があれば resume チェーン
516
542
  if (this.sessionId) options.resume = this.sessionId
517
543
 
@@ -613,18 +639,21 @@ class ClaudeStreamSession {
613
639
  }
614
640
  const next = this._pendingMessages.shift()
615
641
  this._emitQueueState([next.text])
616
- this._runPerMessage(next.text).catch((err) => {
617
- this.logger?.error(
618
- { stream_id: this.stream_id, err: err?.message },
619
- "drain runPerMessage threw",
620
- )
621
- })
642
+ this._runPerMessage(next.text, { ultracode: next.ultracode === true }).catch(
643
+ (err) => {
644
+ this.logger?.error(
645
+ { stream_id: this.stream_id, err: err?.message },
646
+ "drain runPerMessage threw",
647
+ )
648
+ },
649
+ )
622
650
  }
623
651
 
624
- /** キャンセル機能 (0.6.26): pending キューへ安定 id 付きで 1 件積む。 */
625
- _enqueuePending(prompt) {
652
+ /** キャンセル機能 (0.6.26): pending キューへ安定 id 付きで 1 件積む。
653
+ * ultracode (0.6.28): ワンショット ultracode フラグもエントリに保持し、drain 時に伝播する。 */
654
+ _enqueuePending(prompt, ultracode = false) {
626
655
  const id = `q${++this._queueSeq}`
627
- this._pendingMessages.push({ id, text: prompt })
656
+ this._pendingMessages.push({ id, text: prompt, ultracode: ultracode === true })
628
657
  return id
629
658
  }
630
659
 
@@ -685,9 +714,38 @@ class ClaudeStreamSession {
685
714
  })
686
715
  }
687
716
 
717
+ /** ultracode (0.6.28): 常駐 query の ultracode 状態を目標値へ寄せる (差分時のみ
718
+ * applyFlagSettings を発行)。push の前に await して、設定適用前にターンが消費される
719
+ * レースを防ぐ。query 未起動 / applyFlagSettings 非対応 SDK では no-op。 */
720
+ async _reconcileResidentUltracode(desired) {
721
+ const want = desired === true
722
+ if (want === this._ultracodeCurrent) return
723
+ const q = this._residentQuery
724
+ if (!q || typeof q.applyFlagSettings !== "function") return
725
+ try {
726
+ await q.applyFlagSettings(
727
+ want
728
+ ? { ultracode: true, enableWorkflows: true }
729
+ : { ultracode: false },
730
+ )
731
+ this._ultracodeCurrent = want
732
+ this.logger?.info(
733
+ { stream_id: this.stream_id, ultracode: want },
734
+ "resident ultracode reconciled",
735
+ )
736
+ } catch (err) {
737
+ this.logger?.warn(
738
+ { stream_id: this.stream_id, err: err?.message },
739
+ "applyFlagSettings ultracode failed",
740
+ )
741
+ }
742
+ }
743
+
688
744
  /** 改修4 (A): ターン完了時に pending の先頭 1 件を InputQueue へ流す (ターンのシリアライズ)。
689
- * queue_state を更新して送信待ちチップを drain させる (frontend がバブル昇格する)。 */
690
- _drainResidentPending() {
745
+ * queue_state を更新して送信待ちチップを drain させる (frontend がバブル昇格する)。
746
+ * ultracode (0.6.28): 次ターンの目標 ultracode 状態へ reconcile してから push する
747
+ * (await するため async 化。result ハンドラからは fire-and-forget で呼ばれる)。 */
748
+ async _drainResidentPending() {
691
749
  if (this._closed) return
692
750
  if (this._pendingMessages.length === 0) {
693
751
  this._emitQueueState()
@@ -695,6 +753,7 @@ class ClaudeStreamSession {
695
753
  }
696
754
  const next = this._pendingMessages.shift()
697
755
  this._busy = true
756
+ await this._reconcileResidentUltracode(next.ultracode === true)
698
757
  this._inputQueue.push(toSDKUserMessage(next.text))
699
758
  this._emitQueueState([next.text])
700
759
  }
@@ -718,6 +777,10 @@ class ClaudeStreamSession {
718
777
  if (this.permissionMode) options.permissionMode = this.permissionMode
719
778
  if (this.maxTurns != null) options.maxTurns = this.maxTurns
720
779
  this._applyThinkingOptions(options)
780
+ // ultracode (0.6.28): 新規 query は flag settings 既定 (ultracode=off) で始まる。
781
+ // 適用済み状態の追跡を false にリセットし、次ターンの reconcile が正しく差分判定できる
782
+ // ようにする (異常終了→resume 再起動時にも確実にリセット)。
783
+ this._ultracodeCurrent = false
721
784
  // 改修4: 起動時に sessionId (= resumeSessionId) があれば resume チェーンで文脈を引き継ぐ。
722
785
  // query 起動時点の値のみ有効 (起動後に確定/変化する session_id は同一 query 内で継続される)。
723
786
  if (this.sessionId) options.resume = this.sessionId
@@ -748,7 +811,14 @@ class ClaudeStreamSession {
748
811
  denyPending("turn ended")
749
812
  this._busy = false
750
813
  // 改修4 (A): シリアライズした pending があれば次の 1 件を InputQueue へ流す。
751
- this._drainResidentPending()
814
+ // ultracode (0.6.28): _drainResidentPending は applyFlagSettings を await するため
815
+ // async。result ハンドラ (for await ループ内) からは fire-and-forget で呼ぶ。
816
+ this._drainResidentPending().catch((err) =>
817
+ this.logger?.warn(
818
+ { stream_id: this.stream_id, err: err?.message },
819
+ "drainResidentPending threw",
820
+ ),
821
+ )
752
822
  }
753
823
  try {
754
824
  this.onEvent?.(msg)
@@ -1030,14 +1100,15 @@ export class ClaudeStreamBridge extends EventEmitter {
1030
1100
  }
1031
1101
 
1032
1102
  /** browser → claude の user メッセージ。1 件 = 1 query (resume チェーン)。 */
1033
- input({ stream_id, message }) {
1103
+ input({ stream_id, message, ultracode }) {
1034
1104
  const s = this.sessions.get(stream_id)
1035
1105
  if (!s) {
1036
1106
  this.logger?.warn({ stream_id }, "claude.input but stream missing")
1037
1107
  return false
1038
1108
  }
1039
1109
  // 非同期でターン実行 (完了は result イベント + onEvent 経由で browser に届く)
1040
- s.sendMessage(message).catch((err) => {
1110
+ // ultracode (0.6.28): このメッセージのみ ultracode ワンショットを適用するフラグ。
1111
+ s.sendMessage(message, { ultracode: ultracode === true }).catch((err) => {
1041
1112
  this.logger?.error(
1042
1113
  { stream_id, err: err?.message },
1043
1114
  "claude sendMessage threw unexpectedly",
package/src/main.mjs CHANGED
@@ -781,6 +781,9 @@ async function dispatch(msg, ctx) {
781
781
  ctx.claudeBridge.input({
782
782
  stream_id: msg.stream_id,
783
783
  message: msg.message,
784
+ // ultracode (0.6.28): browser がこのメッセージ単位で送るワンショット指定。
785
+ // true のときだけそのターンを xhigh effort + dynamic-workflow で実行する。
786
+ ultracode: msg.ultracode === true,
784
787
  })
785
788
  return
786
789
  case "claude.upload": {