@cocorograph/hub-agent 0.6.94 → 0.6.96
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 +52 -2
- package/src/proc-introspect.mjs +18 -1
- package/src/state.mjs +6 -1
package/package.json
CHANGED
package/src/main.mjs
CHANGED
|
@@ -1021,9 +1021,26 @@ function startReadinessLoop({ tracker, logger, intervalMs = 700 }) {
|
|
|
1021
1021
|
* pty.exit 受信時に処理する)
|
|
1022
1022
|
* - tmux 自体が動いてない場合 (listSessionStates → []) は何も push しない
|
|
1023
1023
|
*/
|
|
1024
|
+
// (i) 出力フロー検知の有効窓。ペイン出力領域の署名 (output_sig) が直近この時間内に変化して
|
|
1025
|
+
// いれば「出力ストリーミング中」とみなし proc_busy に OR する。state loop は 5s 周期なので、
|
|
1026
|
+
// 連続生成中は毎 tick 署名が変わり常時 true、出力が止まれば次 tick (≤5s) で署名不変になり
|
|
1027
|
+
// 窓経過後に false へ落ちる (ターン終了後のスピナー固着=症状A を悪化させない短さ)。
|
|
1028
|
+
const OUTPUT_ACTIVE_MS = Number(process.env.HUB_AGENT_OUTPUT_ACTIVE_MS ?? 8000)
|
|
1029
|
+
|
|
1030
|
+
// 権威的な「応答停止 (ハング)」判定の無進捗しきい値。ターン進行中 (armed) なのに、実際の
|
|
1031
|
+
// 進捗 (子プロセス実行中 / 出力フロー / ペインのライブ processing) がこの時間継続して観測
|
|
1032
|
+
// されなければ stalled とする。frontend 側の jsonl 無音タイマー (長時間ツールで誤検知し、
|
|
1033
|
+
// 本当のハングでは turnActive クリアにより見逃す) を置き換える正本。frontend の
|
|
1034
|
+
// TURN_STALL_WARN_MS と揃える。
|
|
1035
|
+
const STALL_WARN_MS = Number(process.env.HUB_AGENT_STALL_WARN_MS ?? 90000)
|
|
1036
|
+
|
|
1024
1037
|
function startStateLoop({ client, plugins, logger, intervalMs, claudeBridge, readinessTracker }) {
|
|
1025
1038
|
const lastByName = new Map() // session_name → {status, context_pct}
|
|
1026
1039
|
const lastTurnAtByName = new Map() // session_name → 最後に event ファイル化した turnAt
|
|
1040
|
+
// (i) session_name → {sig, changedAt}: 出力領域署名の最終変化時刻 (出力フロー検知用)。
|
|
1041
|
+
const outputFlowByName = new Map()
|
|
1042
|
+
// 権威的 stall 判定: session_name → 最後に「進捗あり」と観測した時刻 (ms)。
|
|
1043
|
+
const lastProgressAtByName = new Map()
|
|
1027
1044
|
let stopped = false
|
|
1028
1045
|
|
|
1029
1046
|
// RC-8: WS 再接続のたびに差分送信の基準 (lastByName) をクリアし、次 tick で全 session.state を
|
|
@@ -1093,7 +1110,37 @@ function startStateLoop({ client, plugins, logger, intervalMs, claudeBridge, rea
|
|
|
1093
1110
|
// プロセス内省由来の busy レベル (Stop フック/ツール実行中=true)。frontend は
|
|
1094
1111
|
// これでペイン由来の早期消灯を抑止し、tool/Stop フック中もスピナーを保つ。
|
|
1095
1112
|
// 高速 readiness loop が維持する値をそのまま相乗せする (この loop では計算しない)。
|
|
1096
|
-
const
|
|
1113
|
+
const childBusy = readinessTracker ? readinessTracker.getBusy(s.session_name) : false
|
|
1114
|
+
// (i) 出力フロー検知: proc 内省は pane_pid 子孫の「プロセス数」で busy を見るため、
|
|
1115
|
+
// ツールを起動しない純テキスト生成 (モデルが出力を流すだけ) を busy として検出できない。
|
|
1116
|
+
// その間に capture-pane のスピナー検出が 1 フレーム取りこぼすと frontend がアイドルと
|
|
1117
|
+
// 誤読してスピナーを消す (生成中なのに消える=症状B)。出力領域の署名が直近変化していれば
|
|
1118
|
+
// 「出力中」とみなし proc_busy に OR する。childBusy=false の純テキスト生成中も
|
|
1119
|
+
// proc_busy=true となり frontend のペイン由来消灯が抑止される。出力が止まれば署名が
|
|
1120
|
+
// 不変になり窓経過後に false へ落ちるので、ターン終了後のスピナー固着は悪化させない。
|
|
1121
|
+
let outputActive = false
|
|
1122
|
+
if (typeof s.output_sig === "string") {
|
|
1123
|
+
const prevFlow = outputFlowByName.get(s.session_name)
|
|
1124
|
+
let changedAt = prevFlow ? prevFlow.changedAt : 0
|
|
1125
|
+
if (!prevFlow || prevFlow.sig !== s.output_sig) changedAt = Date.now()
|
|
1126
|
+
outputFlowByName.set(s.session_name, { sig: s.output_sig, changedAt })
|
|
1127
|
+
outputActive = Date.now() - changedAt < OUTPUT_ACTIVE_MS
|
|
1128
|
+
}
|
|
1129
|
+
const procBusy = childBusy || outputActive
|
|
1130
|
+
// 権威的 stall 判定。armed (ターン進行中=prompt_submit 受信後 stop/ready 未) なのに
|
|
1131
|
+
// 進捗 (procBusy=子プロセス/出力フロー or ペインの live processing) が STALL_WARN_MS
|
|
1132
|
+
// 継続して無いとき stalled=true。armed でない / 進捗ありのときは進捗時刻を更新して
|
|
1133
|
+
// 無進捗カウントをリセットする。frontend はこれを応答停止バナーの正本に使う。
|
|
1134
|
+
const armed = readinessTracker
|
|
1135
|
+
? readinessTracker.isArmed(s.session_name)
|
|
1136
|
+
: false
|
|
1137
|
+
const nowMs = Date.now()
|
|
1138
|
+
const progressing = procBusy || status === "processing"
|
|
1139
|
+
if (progressing || !armed) {
|
|
1140
|
+
lastProgressAtByName.set(s.session_name, nowMs)
|
|
1141
|
+
}
|
|
1142
|
+
const lastProgressAt = lastProgressAtByName.get(s.session_name) ?? nowMs
|
|
1143
|
+
const stalled = armed && nowMs - lastProgressAt >= STALL_WARN_MS
|
|
1097
1144
|
const prev = lastByName.get(s.session_name)
|
|
1098
1145
|
if (
|
|
1099
1146
|
!prev ||
|
|
@@ -1101,7 +1148,8 @@ function startStateLoop({ client, plugins, logger, intervalMs, claudeBridge, rea
|
|
|
1101
1148
|
prev.context_pct !== contextPct ||
|
|
1102
1149
|
prev.permission_mode !== permissionMode ||
|
|
1103
1150
|
prev.stable !== stable ||
|
|
1104
|
-
prev.proc_busy !== procBusy
|
|
1151
|
+
prev.proc_busy !== procBusy ||
|
|
1152
|
+
prev.stalled !== stalled
|
|
1105
1153
|
) {
|
|
1106
1154
|
lastByName.set(s.session_name, {
|
|
1107
1155
|
status,
|
|
@@ -1109,6 +1157,7 @@ function startStateLoop({ client, plugins, logger, intervalMs, claudeBridge, rea
|
|
|
1109
1157
|
permission_mode: permissionMode,
|
|
1110
1158
|
stable,
|
|
1111
1159
|
proc_busy: procBusy,
|
|
1160
|
+
stalled,
|
|
1112
1161
|
})
|
|
1113
1162
|
client.send({
|
|
1114
1163
|
type: "session.state",
|
|
@@ -1118,6 +1167,7 @@ function startStateLoop({ client, plugins, logger, intervalMs, claudeBridge, rea
|
|
|
1118
1167
|
permission_mode: permissionMode,
|
|
1119
1168
|
stable,
|
|
1120
1169
|
proc_busy: procBusy,
|
|
1170
|
+
stalled,
|
|
1121
1171
|
})
|
|
1122
1172
|
}
|
|
1123
1173
|
}
|
package/src/proc-introspect.mjs
CHANGED
|
@@ -283,7 +283,18 @@ export class ReadinessTracker {
|
|
|
283
283
|
s.grace = this.graceSamples
|
|
284
284
|
s.clearStreak = 0
|
|
285
285
|
s.activeStreak = 0
|
|
286
|
-
|
|
286
|
+
// cap = 140s armed のまま settle しない異常状態の後始末。stopSeen 済 (= モデルのターンは
|
|
287
|
+
// 終わっている) なら、ここで ready を 1 回だけ出して frontend のスピナー固着とキュー詰まり
|
|
288
|
+
// (ready 不発で turnOverride=true が畳めず drain されない) を解く。ユーザーが Claude 経由で
|
|
289
|
+
// dev server 等を起動しっぱなしにし、その子プロセスで count>baseline が永続して busy が
|
|
290
|
+
// 解けないケース (症状A の主因) を救済する。stopSeen=false (stop 取りこぼし or 本当に 140s
|
|
291
|
+
// 走行中) のときは出さない (生成中への誤フラッシュ防止)。
|
|
292
|
+
let ready = false
|
|
293
|
+
if (s.stopSeen && !s.emitted) {
|
|
294
|
+
ready = true
|
|
295
|
+
s.emitted = true
|
|
296
|
+
}
|
|
297
|
+
return { busy: false, ready }
|
|
287
298
|
}
|
|
288
299
|
// ターン中: baseline は上げない (一過性ツールを新 idle に化けさせない)。より低い床は学習。
|
|
289
300
|
if (count < s.baseline) s.baseline = count
|
|
@@ -313,6 +324,12 @@ export class ReadinessTracker {
|
|
|
313
324
|
return this.byName.get(name)?.busy ?? false
|
|
314
325
|
}
|
|
315
326
|
|
|
327
|
+
/** ターン進行中か (prompt_submit で arm 後、ready/cap で disarm するまで true)。
|
|
328
|
+
* 生成中・Stop フック中の両方を含む。state loop が権威的な stall 判定に使う。 */
|
|
329
|
+
isArmed(name) {
|
|
330
|
+
return this.byName.get(name)?.armed ?? false
|
|
331
|
+
}
|
|
332
|
+
|
|
316
333
|
/** readiness loop が観測対象に含めるべきか (armed / busy / grace 中)。 */
|
|
317
334
|
isActive(name) {
|
|
318
335
|
const s = this.byName.get(name)
|
package/src/state.mjs
CHANGED
|
@@ -531,7 +531,7 @@ const _stabilityByName = new Map() // session_name → { sig, since }
|
|
|
531
531
|
|
|
532
532
|
/** ペインの出力領域 (入力欄・揮発フッターを除いた上側) の軽量シグネチャ (FNV-1a 32bit)。
|
|
533
533
|
* 末尾 8 行 (入力欄 + スピナー経過秒/ヒント等) を除くので、idle 中は連続 capture が一致する。 */
|
|
534
|
-
function _outputSignature(text) {
|
|
534
|
+
export function _outputSignature(text) {
|
|
535
535
|
const lines = text.split("\n")
|
|
536
536
|
const body = lines.slice(0, Math.max(0, lines.length - 8)).join("\n")
|
|
537
537
|
let h = 0x811c9dc5
|
|
@@ -600,6 +600,10 @@ export async function detectSessionState(sessionName, opts = {}) {
|
|
|
600
600
|
permission_mode:
|
|
601
601
|
hookResult.result.permission_mode ?? defaultPermissionMode,
|
|
602
602
|
stable: _computeStable(sessionName, status, text),
|
|
603
|
+
// 出力領域 (揮発フッター除外) の署名。state loop が tick 間で比較し、純テキスト
|
|
604
|
+
// 生成 (子プロセスを spawn しないため proc 内省が busy=false と見るケース) を
|
|
605
|
+
// 「出力フロー中=busy」として検出するのに使う。
|
|
606
|
+
output_sig: _outputSignature(text),
|
|
603
607
|
}
|
|
604
608
|
}
|
|
605
609
|
}
|
|
@@ -610,6 +614,7 @@ export async function detectSessionState(sessionName, opts = {}) {
|
|
|
610
614
|
context_pct: defaultContextPct,
|
|
611
615
|
permission_mode: defaultPermissionMode,
|
|
612
616
|
stable: _computeStable(sessionName, status, text),
|
|
617
|
+
output_sig: _outputSignature(text),
|
|
613
618
|
}
|
|
614
619
|
}
|
|
615
620
|
|