@tyhld/conductor 0.12.99 → 0.12.100
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 +98 -4
- package/package.json +1 -1
package/dist/selfcheck.js
CHANGED
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
* 【★副作用なし】commit・push・PR・合流・配備・npm 公開をしない。作業場所は一時フォルダ
|
|
19
19
|
* (~/.local/state/conductor/selfcheck/run-<時刻>・git の中ではない)で、終わったら消す。
|
|
20
20
|
*
|
|
21
|
+
* 【★落ちたときは理由を残す(便 5a94ba06)】小さな headless 試験が落ちたら、その項目の detail に
|
|
22
|
+
* 「結果の行の本文・stderr の末尾・終了コード」を残す(控えにも管制にも同じ中身が載る)。
|
|
23
|
+
* ④囲い・⑤通信の ✗ が「小さな試験が先に落ちたので確かめられなかった」ときは、そう読める文にする。
|
|
24
|
+
* ★合否と、落ちたら戻す動きは1つも変えない(FAIL は FAIL・やり直しを足さない)。
|
|
25
|
+
*
|
|
21
26
|
* 【結果】手元の控え(~/.local/state/conductor/selfcheck/last.json と history/)に必ず残す。
|
|
22
27
|
* 管制の口が在れば送る(★口が無い・届かないときも落ちない=送れなかったことを控えに書くだけ)。
|
|
23
28
|
* 送る中身は buildSelfcheckPayload の1か所(口の形は ai-kana の便 2f5eb92c が決めた形・便 29babc5d で合わせた)。
|
|
@@ -81,6 +86,46 @@ export const SELFCHECK_MODEL = 'haiku';
|
|
|
81
86
|
/** 小さな試験の最後の答えを示す目印(★職人の言葉ではなく、tool の結果の中で探す)。 */
|
|
82
87
|
export const PROBE_OK_MARK = 'SELFCHECK_PROBE_OK';
|
|
83
88
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
89
|
+
// 落ちたときの手がかり(便 5a94ba06)
|
|
90
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
91
|
+
/**
|
|
92
|
+
* 控えと管制に残す手がかり1つあたりの長さの上限(★長い出力をそのまま流し込まない)。
|
|
93
|
+
*/
|
|
94
|
+
export const SELFCHECK_DETAIL_MAX = 500;
|
|
95
|
+
/**
|
|
96
|
+
* 鍵・トークンらしい並びを伏せる(★控えにも管制にも残す文なので、残す前に必ず通す)。
|
|
97
|
+
* ここで見るのは「見ただけで鍵と分かる形」だけ。分からない並びは伏せない代わりに、
|
|
98
|
+
* 手がかりは長さを切って残す(safeDetail)。
|
|
99
|
+
*/
|
|
100
|
+
const SECRET_PATTERNS = [
|
|
101
|
+
{ re: /Bearer\s+[A-Za-z0-9._~+/=-]{8,}/gi, to: 'Bearer ***' },
|
|
102
|
+
{ re: /\beyJ[A-Za-z0-9._-]{12,}/g, to: 'eyJ***' },
|
|
103
|
+
{ re: /\b(sk|pk|rk)-[A-Za-z0-9._-]{8,}/gi, to: '$1-***' },
|
|
104
|
+
{ re: /\b(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{8,}/g, to: '$1_***' },
|
|
105
|
+
{ re: /\bgithub_pat_[A-Za-z0-9_]{8,}/g, to: 'github_pat_***' },
|
|
106
|
+
{ re: /\bnpm_[A-Za-z0-9]{8,}/g, to: 'npm_***' },
|
|
107
|
+
{ re: /\bxox[abeporsu]-[A-Za-z0-9-]{8,}/gi, to: 'xox-***' },
|
|
108
|
+
{ re: /\bAKIA[0-9A-Z]{12,}/g, to: 'AKIA***' },
|
|
109
|
+
];
|
|
110
|
+
/** 秘密らしい並びを伏せる。 */
|
|
111
|
+
export function redactSecrets(raw) {
|
|
112
|
+
let s = raw;
|
|
113
|
+
for (const p of SECRET_PATTERNS)
|
|
114
|
+
s = s.replace(p.re, p.to);
|
|
115
|
+
return s;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 手がかりを残せる形にする(伏せる → 1行にする → 長さを切る)。
|
|
119
|
+
* from='tail' は末尾を残す(stderr は終わりに理由が出るため)。
|
|
120
|
+
* ★返り値は必ず max 字以下。
|
|
121
|
+
*/
|
|
122
|
+
export function safeDetail(raw, from = 'head', max = SELFCHECK_DETAIL_MAX) {
|
|
123
|
+
const one = redactSecrets(raw).replace(/\s+/g, ' ').trim();
|
|
124
|
+
if (one.length <= max)
|
|
125
|
+
return one;
|
|
126
|
+
return from === 'tail' ? `…${one.slice(one.length - (max - 1))}` : `${one.slice(0, max - 1)}…`;
|
|
127
|
+
}
|
|
128
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
84
129
|
// 小さな試験(⑥)の中身と読み方
|
|
85
130
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
86
131
|
/**
|
|
@@ -146,6 +191,7 @@ export function summarizeStream(stdout) {
|
|
|
146
191
|
out.result = {
|
|
147
192
|
subtype: typeof ev.subtype === 'string' ? ev.subtype : null,
|
|
148
193
|
isError: ev.is_error === true,
|
|
194
|
+
text: typeof ev.result === 'string' ? ev.result : typeof ev.error === 'string' ? ev.error : null,
|
|
149
195
|
costUsd: num(ev.total_cost_usd),
|
|
150
196
|
inputTokens: num(usage.input_tokens) === null
|
|
151
197
|
? null
|
|
@@ -533,6 +579,47 @@ function siteSettingsFor(distRoot) {
|
|
|
533
579
|
return null;
|
|
534
580
|
}
|
|
535
581
|
}
|
|
582
|
+
/** 小さな試験が AI まで届いたか(使用量が1つでも在れば届いた)。 */
|
|
583
|
+
function reachedModel(h) {
|
|
584
|
+
const u = h.summary?.result;
|
|
585
|
+
if (!u)
|
|
586
|
+
return false;
|
|
587
|
+
return (u.inputTokens ?? 0) > 0 || (u.outputTokens ?? 0) > 0 || (u.costUsd ?? 0) > 0;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* 小さな試験が落ちたときに、④囲い・⑤通信の ✗ に添える文(便 5a94ba06 の2)。
|
|
591
|
+
* ★「確かめられなかった」のは、小さな試験が先に落ちたせい=そう読める文にする。
|
|
592
|
+
* ★判定(FAIL)は1つも変えない。変えるのは読む文だけ。
|
|
593
|
+
*/
|
|
594
|
+
export function unverifiedReason(h) {
|
|
595
|
+
if (!h.ran)
|
|
596
|
+
return `小さな試験を流していないため、確かめられませんでした(${safeDetail(h.error ?? '理由不明')})`;
|
|
597
|
+
if (h.probeRan)
|
|
598
|
+
return '小さな試験は動きましたが、台本の出力にこの行がなかったため、確かめられませんでした';
|
|
599
|
+
if (!reachedModel(h))
|
|
600
|
+
return '小さな試験が AI に届く前に終わったため、確かめられませんでした';
|
|
601
|
+
return '小さな試験の中で台本が動かなかったため、確かめられませんでした';
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* 小さな試験が落ちたときの手がかり(便 5a94ba06 の1)。
|
|
605
|
+
*
|
|
606
|
+
* 【なぜ要るか(実物 2026-10-04 18:23Z・TYHLD-OTA)】0.12.98 の自己診断が落ちて 0.12.92 へ自動で戻ったが、
|
|
607
|
+
* 落ちた3項目(sandbox・network・headless)はどれも「小さな試験1回」の結果から出ていて、
|
|
608
|
+
* その1回が【なぜ】落ちたかの文が、手元の控えにも管制にも1つも残っていなかった
|
|
609
|
+
* (終了コード 1・結果の行が success で is_error=true・使用量 0)。そのため版の不具合か
|
|
610
|
+
* その場かぎりの失敗かを決められなかった。ここで残すのは、次の3つだけ。
|
|
611
|
+
* ・結果の行の本文(result の文) ・stderr の末尾 ・終了コード
|
|
612
|
+
* ★残す文は safeDetail を通す(鍵・トークンらしい並びは伏せ、1つ 500字で切る)。
|
|
613
|
+
* ★合否は1つも変えない(これは detail に足す文だけ)。通ったときは1行も足さない。
|
|
614
|
+
*/
|
|
615
|
+
export function headlessFailureDetail(h) {
|
|
616
|
+
const out = [];
|
|
617
|
+
const text = h.summary?.result?.text ?? '';
|
|
618
|
+
out.push(text.trim() ? `手がかり: 結果の行の本文 ${safeDetail(text)}` : '手がかり: 結果の行に本文がありません');
|
|
619
|
+
out.push(h.stderr.trim() ? `手がかり: stderr の末尾 ${safeDetail(h.stderr, 'tail')}` : '手がかり: stderr は空です');
|
|
620
|
+
out.push(`手がかり: 終了コード ${h.exitCode === null ? '不明' : h.exitCode}`);
|
|
621
|
+
return out;
|
|
622
|
+
}
|
|
536
623
|
/** 小さな試験と MCP の確かめで claude に渡す環境(★中継と同じ形・src/headless.ts)。 */
|
|
537
624
|
function probeEnv(d, claudeBin) {
|
|
538
625
|
const env = { ...d.env };
|
|
@@ -543,7 +630,7 @@ function probeEnv(d, claudeBin) {
|
|
|
543
630
|
}
|
|
544
631
|
/** ⑥ 小さな headless 試験を1回流す。 */
|
|
545
632
|
function runHeadlessProbe(d, claudeBin, runDir) {
|
|
546
|
-
const res = { summary: null, exitCode: null, outsideWrite: 'unknown', net: {}, probeRan: false, error: null };
|
|
633
|
+
const res = { summary: null, exitCode: null, outsideWrite: 'unknown', net: {}, probeRan: false, error: null, ran: false, stderr: '' };
|
|
547
634
|
if (!claudeBin) {
|
|
548
635
|
res.error = 'claude が無いので流していません';
|
|
549
636
|
return res;
|
|
@@ -570,7 +657,9 @@ function runHeadlessProbe(d, claudeBin, runDir) {
|
|
|
570
657
|
// ★中継と同じ許可(src/headless.ts の既定)=番人がそのまま効く。迂回の旗は付けない。
|
|
571
658
|
'--permission-mode', 'acceptEdits',
|
|
572
659
|
], { cwd: work, env, timeoutMs: SELFCHECK_HEADLESS_TIMEOUT_MS });
|
|
660
|
+
res.ran = true;
|
|
573
661
|
res.exitCode = r.status;
|
|
662
|
+
res.stderr = r.stderr;
|
|
574
663
|
if (r.error)
|
|
575
664
|
res.error = r.error;
|
|
576
665
|
res.summary = summarizeStream(r.stdout);
|
|
@@ -746,7 +835,7 @@ export async function runSelfcheck(d, trigger) {
|
|
|
746
835
|
const h = runHeadlessProbe(d, claude.bin, runDir);
|
|
747
836
|
const sum = h.summary;
|
|
748
837
|
if (h.error)
|
|
749
|
-
note(headless, h.error);
|
|
838
|
+
note(headless, safeDetail(h.error));
|
|
750
839
|
if (!sum || !sum.result)
|
|
751
840
|
fail(headless, 'fault', `結果の行がありません(終了 ${h.exitCode})`);
|
|
752
841
|
else {
|
|
@@ -763,6 +852,11 @@ export async function runSelfcheck(d, trigger) {
|
|
|
763
852
|
else
|
|
764
853
|
fail(headless, 'fault', 'tool の結果に台本の目印がありません(Bash が動いていない)');
|
|
765
854
|
}
|
|
855
|
+
// ★落ちたときは「なぜ落ちたか」の手がかりを必ず残す(控えにも管制にも・便 5a94ba06)。
|
|
856
|
+
// 通ったときは1行も足さない(出力は今までと同じ)。
|
|
857
|
+
if (headless.ok === false)
|
|
858
|
+
for (const line of headlessFailureDetail(h))
|
|
859
|
+
note(headless, line);
|
|
766
860
|
// ③ MCP(★落ち着くまで確かめる・便が実際に使う登録を見る=settleMcp)
|
|
767
861
|
const mcp = await settleMcp(d, claude.bin, path.join(runDir, 'work'), sum?.mcp ?? null);
|
|
768
862
|
const seen = mcp.source === 'mcp-list' ? `claude mcp list で確かめた・${mcp.tries}回目` : mcp.source === 'init' ? '小さな試験の最初の行' : '一覧なし';
|
|
@@ -797,11 +891,11 @@ export async function runSelfcheck(d, trigger) {
|
|
|
797
891
|
else if (h.outsideWrite === 'open')
|
|
798
892
|
fail(sandbox, 'fault', '★囲いの中から作業場所の外へ書けました(囲いが効いていない)');
|
|
799
893
|
else
|
|
800
|
-
fail(sandbox, 'fault',
|
|
894
|
+
fail(sandbox, 'fault', `囲いの中の書き込み: ${unverifiedReason(h)}`);
|
|
801
895
|
// ⑤ 内側の通信
|
|
802
896
|
const codes = Object.entries(h.net);
|
|
803
897
|
if (codes.length === 0)
|
|
804
|
-
fail(network, 'fault',
|
|
898
|
+
fail(network, 'fault', `囲いの内側からの通信: ${unverifiedReason(h)}`);
|
|
805
899
|
for (const [u, c] of codes) {
|
|
806
900
|
if (/^[23]\d\d$/.test(c))
|
|
807
901
|
pass(network, `囲いの内側から ${u} → ${c}`);
|