@tyhld/conductor 0.12.103 → 0.12.104
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/dist/selfcheck.js +122 -32
- package/package.json +1 -1
package/dist/selfcheck.js
CHANGED
|
@@ -21,7 +21,13 @@
|
|
|
21
21
|
* 【★落ちたときは理由を残す(便 5a94ba06)】小さな headless 試験が落ちたら、その項目の detail に
|
|
22
22
|
* 「結果の行の本文・stderr の末尾・終了コード」を残す(控えにも管制にも同じ中身が載る)。
|
|
23
23
|
* ④囲い・⑤通信の ✗ が「小さな試験が先に落ちたので確かめられなかった」ときは、そう読める文にする。
|
|
24
|
-
*
|
|
24
|
+
*
|
|
25
|
+
* 【★既知の一時障害1種類だけ、60秒後に1回だけやり直す(えふさん確定 2026-10-05)】
|
|
26
|
+
* 小さな headless 試験が落ちたら理由を分類し、Claude Code の「認証の更新が別の Claude Code の
|
|
27
|
+
* 処理と重なった」と【確かめられた】失敗のときだけ、60秒待って1回だけやり直す。2回目が通れば
|
|
28
|
+
* その自己診断は合格(④囲い・⑤通信も2回目の結果で判定)。再失敗・別種類の失敗は、今までどおり
|
|
29
|
+
* 即 FAIL=戻して配布を止める。★2回目のやり直しは無い。★「2回目が通った=版は正常」とは
|
|
30
|
+
* 一般化しない(この1種類だけの例外)。やり直した事実は ⑥ の detail に残す(控えにも管制にも)。
|
|
25
31
|
*
|
|
26
32
|
* 【結果】手元の控え(~/.local/state/conductor/selfcheck/last.json と history/)に必ず残す。
|
|
27
33
|
* 管制の口が在れば送る(★口が無い・届かないときも落ちない=送れなかったことを控えに書くだけ)。
|
|
@@ -645,6 +651,53 @@ export function headlessFailureDetail(h) {
|
|
|
645
651
|
out.push(`手がかり: 終了コード ${h.exitCode === null ? '不明' : h.exitCode}`);
|
|
646
652
|
return out;
|
|
647
653
|
}
|
|
654
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
655
|
+
// 失敗の分類と、1回だけのやり直し(えふさん確定 2026-10-05)
|
|
656
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
657
|
+
/**
|
|
658
|
+
* 「認証の更新の重なり」と判定できたときだけ待つ間(★60秒・えふさん確定 2026-10-05)。
|
|
659
|
+
* ★やり直しは60秒後に【1回だけ】(2回目のやり直しは無い)。
|
|
660
|
+
*/
|
|
661
|
+
export const SELFCHECK_AUTH_RETRY_WAIT_MS = 60_000;
|
|
662
|
+
/**
|
|
663
|
+
* 「認証の更新の重なり」の文(★実物で確かめた形)。
|
|
664
|
+
*
|
|
665
|
+
* 【実物 2026-10-05】FshimaNP 09:17:28Z(きっかけ after-rollback・0.12.102)と
|
|
666
|
+
* TYHLD-OTA 11:43:43Z(きっかけ after-conductor・0.12.101)の「結果の行の本文」が、どちらも
|
|
667
|
+
* `Failed to refresh OAuth token: another Claude Code process is refreshing it or exited mid-refresh.
|
|
668
|
+
* This is usually transient; retry in a minute, and if it persists close other Claude Code processes
|
|
669
|
+
* or sign in again` だった(終了コード 1・結果の行は subtype=success のまま is_error=true・使用量 0・
|
|
670
|
+
* tool が1つも動かず「AI に届く前に終わった」)。
|
|
671
|
+
* ★2つとも当たることを求める。片方だけ・文が少し違う物は「別種類の失敗」として扱う(やり直さない)。
|
|
672
|
+
*/
|
|
673
|
+
const AUTH_REFRESH_CONFLICT_PATTERNS = [
|
|
674
|
+
/failed to refresh oauth token/i,
|
|
675
|
+
/another\b[^.]{0,60}\bprocess is refreshing it\b/i,
|
|
676
|
+
];
|
|
677
|
+
/**
|
|
678
|
+
* 小さな試験の失敗を分類する(★やり直してよいのは1種類だけ)。
|
|
679
|
+
* 「認証の更新の重なり」と判定するのは、次の【両方】を満たす失敗だけ。
|
|
680
|
+
* (a) 結果の行の本文が、Claude Code の「認証の更新が別の処理と重なった」の文に当たる
|
|
681
|
+
* (b) AI に届く前に終わっている(使用量 0・tool が1つも動いていない)
|
|
682
|
+
* ★迷う物は 'other'(=やり直さない)へ倒す。「2回目が通った=版は正常」とは一般化しない。
|
|
683
|
+
*/
|
|
684
|
+
export function classifyHeadlessFailure(h) {
|
|
685
|
+
// 流していない(claude が無い・雛形を読めない)=この種類ではない。
|
|
686
|
+
if (!h.ran)
|
|
687
|
+
return 'other';
|
|
688
|
+
// (a) 結果の行の本文
|
|
689
|
+
const text = h.summary?.result?.text ?? '';
|
|
690
|
+
if (!text.trim())
|
|
691
|
+
return 'other';
|
|
692
|
+
if (!AUTH_REFRESH_CONFLICT_PATTERNS.every((re) => re.test(text)))
|
|
693
|
+
return 'other';
|
|
694
|
+
// (b) AI に届く前に終わっている
|
|
695
|
+
if (reachedModel(h))
|
|
696
|
+
return 'other';
|
|
697
|
+
if ((h.summary?.toolResults.length ?? 0) > 0)
|
|
698
|
+
return 'other';
|
|
699
|
+
return 'auth-refresh-conflict';
|
|
700
|
+
}
|
|
648
701
|
/** 小さな試験と MCP の確かめで claude に渡す環境(★中継と同じ形・src/headless.ts)。 */
|
|
649
702
|
function probeEnv(d, claudeBin) {
|
|
650
703
|
const env = { ...d.env };
|
|
@@ -653,15 +706,23 @@ function probeEnv(d, claudeBin) {
|
|
|
653
706
|
env.PATH = buildWorkerPath(env.PATH, claudeBin);
|
|
654
707
|
return env;
|
|
655
708
|
}
|
|
656
|
-
/**
|
|
657
|
-
|
|
709
|
+
/**
|
|
710
|
+
* 1回目/やり直しの作業場所の名前(★やり直しは別のフォルダで流す)。
|
|
711
|
+
* 同じフォルダで流すと、1回目に外へ書けてしまった跡(probe.txt)が残り、
|
|
712
|
+
* やり直しの「囲いの外へ書けたか」を誤って open と読む。
|
|
713
|
+
*/
|
|
714
|
+
export function probeWorkName(attempt) {
|
|
715
|
+
return attempt === 1 ? 'work' : 'work-retry2';
|
|
716
|
+
}
|
|
717
|
+
/** ⑥ 小さな headless 試験を1回流す(attempt=2 はやり直しの回)。 */
|
|
718
|
+
function runHeadlessProbe(d, claudeBin, runDir, attempt = 1) {
|
|
658
719
|
const res = { summary: null, exitCode: null, outsideWrite: 'unknown', net: {}, probeRan: false, error: null, ran: false, stderr: '' };
|
|
659
720
|
if (!claudeBin) {
|
|
660
721
|
res.error = 'claude が無いので流していません';
|
|
661
722
|
return res;
|
|
662
723
|
}
|
|
663
|
-
const work = path.join(runDir,
|
|
664
|
-
const outside = path.join(runDir, 'outside');
|
|
724
|
+
const work = path.join(runDir, probeWorkName(attempt));
|
|
725
|
+
const outside = path.join(runDir, attempt === 1 ? 'outside' : 'outside-retry2');
|
|
665
726
|
const outsideFile = path.join(outside, 'probe.txt');
|
|
666
727
|
mkdirSync(path.join(work, '.claude'), { recursive: true });
|
|
667
728
|
mkdirSync(outside, { recursive: true });
|
|
@@ -699,6 +760,38 @@ function runHeadlessProbe(d, claudeBin, runDir) {
|
|
|
699
760
|
res.net[m[1]] = m[2];
|
|
700
761
|
return res;
|
|
701
762
|
}
|
|
763
|
+
/**
|
|
764
|
+
* 小さな試験の1回の結果から ⑥ の項目を作る(★1回目もやり直しも【同じ読み方】で判定する)。
|
|
765
|
+
* 合否は職人の言葉でなく、終了コード・結果の行・tool の結果で決める(便 b7bd5e43 から変えない)。
|
|
766
|
+
*/
|
|
767
|
+
export function judgeHeadlessProbe(h) {
|
|
768
|
+
const item = { key: 'headless', label: '小さな headless 試験', ok: null, detail: [] };
|
|
769
|
+
const sum = h.summary;
|
|
770
|
+
if (h.error)
|
|
771
|
+
note(item, safeDetail(h.error));
|
|
772
|
+
if (!sum || !sum.result)
|
|
773
|
+
fail(item, 'fault', `結果の行がありません(終了 ${h.exitCode})`);
|
|
774
|
+
else {
|
|
775
|
+
if (h.exitCode === 0)
|
|
776
|
+
pass(item, '終了コード 0');
|
|
777
|
+
else
|
|
778
|
+
fail(item, 'fault', `終了コード ${h.exitCode}`);
|
|
779
|
+
if (sum.result.subtype === 'success' && !sum.result.isError)
|
|
780
|
+
pass(item, '結果の行が success');
|
|
781
|
+
else
|
|
782
|
+
fail(item, 'fault', `結果の行が ${sum.result.subtype ?? '不明'}(is_error=${sum.result.isError})`);
|
|
783
|
+
if (h.probeRan)
|
|
784
|
+
pass(item, '囲いの中で Bash の台本が動きました(tool の結果で確認)');
|
|
785
|
+
else
|
|
786
|
+
fail(item, 'fault', 'tool の結果に台本の目印がありません(Bash が動いていない)');
|
|
787
|
+
}
|
|
788
|
+
// ★落ちたときは「なぜ落ちたか」の手がかりを必ず残す(控えにも管制にも・便 5a94ba06)。
|
|
789
|
+
// 通ったときは1行も足さない(出力は今までと同じ)。
|
|
790
|
+
if (item.ok === false)
|
|
791
|
+
for (const line of headlessFailureDetail(h))
|
|
792
|
+
note(item, line);
|
|
793
|
+
return item;
|
|
794
|
+
}
|
|
702
795
|
/** +番人:入っている番人に、止めるべき物と通すべき物を1つずつ判定させる。 */
|
|
703
796
|
function checkGuard(d, work) {
|
|
704
797
|
const item = { key: 'guard', label: '番人', ok: null, detail: [] };
|
|
@@ -856,34 +949,30 @@ export async function runSelfcheck(d, trigger) {
|
|
|
856
949
|
const network = net.item;
|
|
857
950
|
const guard = checkGuard(d, path.join(runDir, 'work'));
|
|
858
951
|
// ⑥ 小さな試験(③の MCP・④の書き込み・⑤の内側の通信もここで見る)
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
952
|
+
//
|
|
953
|
+
// ★1回目が「認証の更新の重なり」と判定できたときだけ、60秒待って【1回だけ】やり直す
|
|
954
|
+
// (えふさん確定 2026-10-05)。2回目が通ればその自己診断は合格=④囲い・⑤通信も2回目の結果で
|
|
955
|
+
// 判定する。2回目が落ちたら(理由が何であっても)FAIL=今までどおり戻す・配布を止める。
|
|
956
|
+
// ★別種類の失敗は1回も待たずに即 FAIL。きっかけ(daily・after-conductor・after-claude・
|
|
957
|
+
// after-rollback)を問わず同じ動きにする。
|
|
958
|
+
let h = runHeadlessProbe(d, claude.bin, runDir, 1);
|
|
959
|
+
let headless = judgeHeadlessProbe(h);
|
|
960
|
+
let probeAttempt = 1;
|
|
961
|
+
if (headless.ok === false && classifyHeadlessFailure(h) === 'auth-refresh-conflict') {
|
|
962
|
+
const sleep = d.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
963
|
+
await sleep(SELFCHECK_AUTH_RETRY_WAIT_MS);
|
|
964
|
+
h = runHeadlessProbe(d, claude.bin, runDir, 2);
|
|
965
|
+
headless = judgeHeadlessProbe(h);
|
|
966
|
+
probeAttempt = 2;
|
|
967
|
+
// ★やり直した事実を残す(控え・管制の両方に・合格した回でも「再試行で通った」と読める)。
|
|
968
|
+
const sec = Math.round(SELFCHECK_AUTH_RETRY_WAIT_MS / 1000);
|
|
969
|
+
note(headless, headless.ok === false
|
|
970
|
+
? `1回目は認証の更新の重なりで落ちました・${sec}秒後の2回目も落ちました`
|
|
971
|
+
: `1回目は認証の更新の重なりで落ちました・${sec}秒後の2回目で通りました`);
|
|
879
972
|
}
|
|
880
|
-
|
|
881
|
-
// 通ったときは1行も足さない(出力は今までと同じ)。
|
|
882
|
-
if (headless.ok === false)
|
|
883
|
-
for (const line of headlessFailureDetail(h))
|
|
884
|
-
note(headless, line);
|
|
973
|
+
const sum = h.summary;
|
|
885
974
|
// ③ MCP(★落ち着くまで確かめる・便が実際に使う登録を見る=settleMcp)
|
|
886
|
-
const mcp = await settleMcp(d, claude.bin, path.join(runDir,
|
|
975
|
+
const mcp = await settleMcp(d, claude.bin, path.join(runDir, probeWorkName(probeAttempt)), sum?.mcp ?? null);
|
|
887
976
|
const seen = mcp.source === 'mcp-list' ? `claude mcp list で確かめた・${mcp.tries}回目` : mcp.source === 'init' ? '小さな試験の最初の行' : '一覧なし';
|
|
888
977
|
const mineOf = (st) => (mcp.list ?? []).filter((s) => /ai-kana/i.test(s.name) && s.status === st).map((s) => s.name);
|
|
889
978
|
if (mcp.state === 'connected') {
|
|
@@ -960,7 +1049,8 @@ export async function runSelfcheck(d, trigger) {
|
|
|
960
1049
|
rmSync(runDir, { recursive: true, force: true });
|
|
961
1050
|
// ★道具(Claude Code)が作業場所ごとに /tmp/claude-<uid>/ の下へ作る記録も消す(便 e9276b86)。
|
|
962
1051
|
// 作業場所は毎回名前が違うので、消さないと自己診断のたびに1つずつ溜まる(ノートPCで9つ)。
|
|
963
|
-
|
|
1052
|
+
// ★やり直しの回の作業場所(work-retry2)も同じように消す。
|
|
1053
|
+
for (const dir of [runDir, path.join(runDir, probeWorkName(1)), path.join(runDir, probeWorkName(2))]) {
|
|
964
1054
|
const trace = claudeTraceDir(dir, d.claudeTmpRoot);
|
|
965
1055
|
if (trace)
|
|
966
1056
|
rmSync(trace, { recursive: true, force: true });
|