@cocorograph/hub-agent 0.6.97 → 0.6.99
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/main.mjs +17 -12
- package/src/state.mjs +75 -1
package/package.json
CHANGED
package/src/main.mjs
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
detectPermissionModeFromText,
|
|
39
39
|
detectSessionState,
|
|
40
40
|
listSessionStates,
|
|
41
|
+
StallTracker,
|
|
41
42
|
} from "./state.mjs"
|
|
42
43
|
import {
|
|
43
44
|
DEFAULT_PROFILE_ID,
|
|
@@ -1050,8 +1051,9 @@ function startStateLoop({ client, plugins, logger, intervalMs, claudeBridge, rea
|
|
|
1050
1051
|
const lastTurnAtByName = new Map() // session_name → 最後に event ファイル化した turnAt
|
|
1051
1052
|
// (i) session_name → {sig, changedAt}: 出力領域署名の最終変化時刻 (出力フロー検知用)。
|
|
1052
1053
|
const outputFlowByName = new Map()
|
|
1053
|
-
// 権威的 stall
|
|
1054
|
-
|
|
1054
|
+
// 権威的 stall 判定 (arm 非依存・cap 非依存)。state loop が観測する signal だけで turn の開閉と
|
|
1055
|
+
// 無進捗を判定する。旧 lastProgressAtByName + armed ゲートの「フック依存 + cap 140s で消える」死角を解消。
|
|
1056
|
+
const stallTracker = new StallTracker({ stallWarnMs: STALL_WARN_MS })
|
|
1055
1057
|
let stopped = false
|
|
1056
1058
|
|
|
1057
1059
|
// RC-8: WS 再接続のたびに差分送信の基準 (lastByName) をクリアし、次 tick で全 session.state を
|
|
@@ -1138,20 +1140,23 @@ function startStateLoop({ client, plugins, logger, intervalMs, claudeBridge, rea
|
|
|
1138
1140
|
outputActive = Date.now() - changedAt < OUTPUT_ACTIVE_MS
|
|
1139
1141
|
}
|
|
1140
1142
|
const procBusy = childBusy || outputActive
|
|
1141
|
-
// 権威的 stall
|
|
1142
|
-
//
|
|
1143
|
-
//
|
|
1144
|
-
//
|
|
1143
|
+
// 権威的 stall 判定 (StallTracker, arm 非依存・cap 非依存)。armed (バンドルフックが効く
|
|
1144
|
+
// セッション) は OR で常に turn 開扱い (従来挙動の上位互換)。フック未発火/旧世代hookセッション
|
|
1145
|
+
// でも観測した processing で turnOpen を立て、frozen スピナー (spinner_present) を維持して proc 子も
|
|
1146
|
+
// 出力も止まったハングを拾う。proc 内省の cap(140s) disarm に依存しないので 140s 以後も消えない。
|
|
1147
|
+
// 旧実装の「armed 依存 + cap で消える」二重死角を解消。frontend はこれを応答停止バナーの正本に使う。
|
|
1145
1148
|
const armed = readinessTracker
|
|
1146
1149
|
? readinessTracker.isArmed(s.session_name)
|
|
1147
1150
|
: false
|
|
1148
1151
|
const nowMs = Date.now()
|
|
1149
|
-
const
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1152
|
+
const { stalled } = stallTracker.observe(s.session_name, {
|
|
1153
|
+
armed,
|
|
1154
|
+
processing: status === "processing",
|
|
1155
|
+
procBusy,
|
|
1156
|
+
spinnerPresent: s.spinner_present === true,
|
|
1157
|
+
stable,
|
|
1158
|
+
now: nowMs,
|
|
1159
|
+
})
|
|
1155
1160
|
const prev = lastByName.get(s.session_name)
|
|
1156
1161
|
if (
|
|
1157
1162
|
!prev ||
|
package/src/state.mjs
CHANGED
|
@@ -169,7 +169,12 @@ function _belowIsOnlyChrome(lines, i) {
|
|
|
169
169
|
for (let j = i + 1; j < lines.length; j++) {
|
|
170
170
|
const ln = lines[j]
|
|
171
171
|
if (ln.trim() === "") continue // 空行
|
|
172
|
-
|
|
172
|
+
// 入力欄の罫線。純粋なダッシュ列 (────) に加え、ラベル付き罫線 (────── ultracode ─ /
|
|
173
|
+
// ──── plan mode ─ 等、effort/モード表示が入る入力欄上端) も chrome 扱いにする。末尾 $ を
|
|
174
|
+
// 外し「行頭が 3+ ボックス罫線文字」で判定する。実機 (2026-06-19): ultracode 表示で入力欄上端が
|
|
175
|
+
// "────── ultracode ─" になり純ダッシュ判定を外れ、これを本文行と誤認 → ライブスピナーを
|
|
176
|
+
// 「本文の引用」と誤判定 → 生成中なのに status=waiting に落ちる事象を確認 (三点リーダー消失の実主因)。
|
|
177
|
+
if (/^\s*─{3,}/.test(ln)) continue
|
|
173
178
|
if (/^\s*[❯>]/.test(ln)) continue // 入力プロンプト
|
|
174
179
|
if (/^\s*⎿/.test(ln)) continue // tips / ツリー装飾行
|
|
175
180
|
if (/^\s{4,}\S/.test(ln)) continue // tips 折り返し等の字下げ継続
|
|
@@ -592,6 +597,10 @@ export async function detectSessionState(sessionName, opts = {}) {
|
|
|
592
597
|
}
|
|
593
598
|
const defaultContextPct = detectContextPctFromText(text)
|
|
594
599
|
const defaultPermissionMode = detectPermissionModeFromText(text)
|
|
600
|
+
// スピナー行 (ライブ or 凍結) が画面に在るか。frozen 降格で status が waiting に落ちても、
|
|
601
|
+
// スピナーが画面に残っている = ターンはクリーンに終わっていない (ハング候補)。StallTracker が
|
|
602
|
+
// 「クリーン idle (スピナー無し) でターンを閉じる」判定に使い、frozen スピナーのハングを拾う。
|
|
603
|
+
const spinnerPresent = workingSpinnerLine(text) !== null
|
|
595
604
|
|
|
596
605
|
if (opts.plugins && opts.plugins.length) {
|
|
597
606
|
const hookResult = await runHookChain(opts.plugins, "transformStatusDetection", {
|
|
@@ -611,6 +620,7 @@ export async function detectSessionState(sessionName, opts = {}) {
|
|
|
611
620
|
permission_mode:
|
|
612
621
|
hookResult.result.permission_mode ?? defaultPermissionMode,
|
|
613
622
|
stable: _computeStable(sessionName, status, text),
|
|
623
|
+
spinner_present: spinnerPresent,
|
|
614
624
|
// 出力領域 (揮発フッター除外) の署名。state loop が tick 間で比較し、純テキスト
|
|
615
625
|
// 生成 (子プロセスを spawn しないため proc 内省が busy=false と見るケース) を
|
|
616
626
|
// 「出力フロー中=busy」として検出するのに使う。
|
|
@@ -625,6 +635,7 @@ export async function detectSessionState(sessionName, opts = {}) {
|
|
|
625
635
|
context_pct: defaultContextPct,
|
|
626
636
|
permission_mode: defaultPermissionMode,
|
|
627
637
|
stable: _computeStable(sessionName, status, text),
|
|
638
|
+
spinner_present: spinnerPresent,
|
|
628
639
|
output_sig: _outputSignature(text),
|
|
629
640
|
}
|
|
630
641
|
}
|
|
@@ -643,4 +654,67 @@ export async function listSessionStates(opts = {}) {
|
|
|
643
654
|
)
|
|
644
655
|
}
|
|
645
656
|
|
|
657
|
+
/**
|
|
658
|
+
* arm 非依存・cap 非依存の「応答停止 (stall)」判定ステート。
|
|
659
|
+
*
|
|
660
|
+
* 背景: 旧実装の stalled は (a) proc 内省の armed (= バンドルフックの prompt_submit 由来) に全依存し、
|
|
661
|
+
* フック未発火/旧世代hookセッションでは永久に false、(b) readiness loop の cap(≈140s)で disarm されると
|
|
662
|
+
* stalled が消える (90〜140s だけ表示で以後恒久消失)、という二重の死角があった。本トラッカーは state loop が
|
|
663
|
+
* 観測する signal だけで turn の開閉と無進捗を判定し、arm にも cap にも依存しない:
|
|
664
|
+
* - turnOpen: processing/procBusy を観測したら true。クリーン idle (非progressing かつ スピナー行が画面に
|
|
665
|
+
* 無く stable) で false。armed (フックが効くセッション) は OR で常に開扱い (従来挙動の上位互換)。
|
|
666
|
+
* - 進捗 (processing or procBusy) があれば lastProgressAt を更新。turn が閉じている間も更新し、次ターンの
|
|
667
|
+
* クロックを毎 tick リセットする。
|
|
668
|
+
* - stalled = turnOpen かつ 無進捗が stallWarnMs 継続。frozen スピナー (画面に在るが status は frozen 降格で
|
|
669
|
+
* waiting) は spinnerPresent=true で turnOpen を維持するため、proc 子が無く出力も止まったハングを拾える。
|
|
670
|
+
* cap に依存しないため 140s 以後も消えない。
|
|
671
|
+
*/
|
|
672
|
+
export class StallTracker {
|
|
673
|
+
constructor({ stallWarnMs = 90_000 } = {}) {
|
|
674
|
+
this.stallWarnMs = Math.max(1000, stallWarnMs)
|
|
675
|
+
/** @type {Map<string, {turnOpen: boolean, lastProgressAt: number|null}>} */
|
|
676
|
+
this.byName = new Map()
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
_get(name) {
|
|
680
|
+
let s = this.byName.get(name)
|
|
681
|
+
if (!s) {
|
|
682
|
+
s = { turnOpen: false, lastProgressAt: null }
|
|
683
|
+
this.byName.set(name, s)
|
|
684
|
+
}
|
|
685
|
+
return s
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* @param {string} name
|
|
690
|
+
* @param {{armed?: boolean, processing?: boolean, procBusy?: boolean,
|
|
691
|
+
* spinnerPresent?: boolean, stable?: boolean, now: number}} sig
|
|
692
|
+
* @returns {{stalled: boolean, turnOpen: boolean}}
|
|
693
|
+
*/
|
|
694
|
+
observe(name, { armed = false, processing = false, procBusy = false, spinnerPresent = false, stable = false, now }) {
|
|
695
|
+
const s = this._get(name)
|
|
696
|
+
const progressing = processing || procBusy
|
|
697
|
+
if (progressing) {
|
|
698
|
+
s.turnOpen = true
|
|
699
|
+
} else if (!spinnerPresent && stable) {
|
|
700
|
+
// クリーン idle: 生成中でなく proc 非busy、スピナーも画面に無く出力安定 = ターン正常終了。
|
|
701
|
+
// frozen スピナー (spinnerPresent=true) や stable 未確定の間は閉じない (ハング候補を維持)。
|
|
702
|
+
s.turnOpen = false
|
|
703
|
+
}
|
|
704
|
+
const turnOpen = armed || s.turnOpen
|
|
705
|
+
if (progressing || !turnOpen) {
|
|
706
|
+
// 進捗あり、または turn が閉じている間はクロックを前進させ続ける (次ターンのクロックを毎 tick 0 に保つ)。
|
|
707
|
+
s.lastProgressAt = now
|
|
708
|
+
} else if (s.lastProgressAt == null) {
|
|
709
|
+
s.lastProgressAt = now
|
|
710
|
+
}
|
|
711
|
+
const stalled = turnOpen && now - s.lastProgressAt >= this.stallWarnMs
|
|
712
|
+
return { stalled, turnOpen }
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
forget(name) {
|
|
716
|
+
this.byName.delete(name)
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
646
720
|
export { STATUSES }
|