@tyhld/conductor 0.12.68 → 0.12.70
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/headless.js +24 -4
- package/package.json +1 -1
- package/sales-template/install.sh +50 -27
- package/sales-template/setup.sh +64 -7
package/dist/headless.js
CHANGED
|
@@ -19,7 +19,7 @@ import { createInterface } from 'node:readline';
|
|
|
19
19
|
import { createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import os from 'node:os';
|
|
22
|
-
import { RELAY_DELIVER_MAX, RELAY_FETCH_TIMEOUT_MS, RELAY_HEARTBEAT_INTERVAL_MS,
|
|
22
|
+
import { CENTRAL_BUSY_HOLD_MAX_MS, RELAY_DELIVER_MAX, RELAY_FETCH_TIMEOUT_MS, RELAY_HEARTBEAT_INTERVAL_MS, RELAY_RESTORE_ORPHANS_ENABLED, buildAnchor, buildSendText, canDeliver, completeWithReport, createRelayState, deliveryReport, fetchCommand, fetchCommandStatus, fetchControl, fetchOrphans, isTerminalCommandStatus, kindOf, markDelivered, noteCentralDown, noteCentralUp, noteReadiness, patchStatus, reconnectDelayMs, selectOrphanToRestore, sendHeartbeat, } from './relay.js';
|
|
23
23
|
const log = (msg) => {
|
|
24
24
|
console.log(`[headless] ${msg}`);
|
|
25
25
|
};
|
|
@@ -471,6 +471,7 @@ export function spawnClaudeProcess(cfg, cmd, sessionId, attempt, isResume) {
|
|
|
471
471
|
exitCode: null,
|
|
472
472
|
exitSignal: null,
|
|
473
473
|
stopped: false,
|
|
474
|
+
timedOut: false,
|
|
474
475
|
eventBuffer: [],
|
|
475
476
|
lastFlushAt: Date.now(),
|
|
476
477
|
lastStopCheckAt: Date.now(),
|
|
@@ -626,6 +627,23 @@ async function checkRunningJob(cfg, state) {
|
|
|
626
627
|
if (job.completed) {
|
|
627
628
|
await flushEvents(cfg, job);
|
|
628
629
|
clearActiveJob(cfg.site);
|
|
630
|
+
if (job.timedOut) {
|
|
631
|
+
// 上限時間で強制停止した → 管制へ理由つきで failed を記録する
|
|
632
|
+
const elapsed = Math.round((Date.now() - job.startedAt) / 1000);
|
|
633
|
+
const reason = `上限時間(${Math.round(CENTRAL_BUSY_HOLD_MAX_MS / 1000)}s)で強制停止 (実行${elapsed}s, exit=${job.exitCode}, signal=${job.exitSignal})`;
|
|
634
|
+
await sendCommandEvents(cfg, job.commandId, [{
|
|
635
|
+
type: 'error',
|
|
636
|
+
text: reason.slice(0, EVENT_TEXT_MAX),
|
|
637
|
+
at: new Date().toISOString(),
|
|
638
|
+
attempt: job.attempt,
|
|
639
|
+
}]);
|
|
640
|
+
await patchStatus(cfg, job.commandId, 'failed', reason.slice(0, EVENT_TEXT_MAX));
|
|
641
|
+
rs.abandoned.add(job.commandId);
|
|
642
|
+
state.job = null;
|
|
643
|
+
rs.lastHeartbeatAt = null;
|
|
644
|
+
log(`WARN site=${cfg.site} id=${job.commandId} ${reason}`);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
629
647
|
if (job.stopped) {
|
|
630
648
|
// stop_requested で止めた
|
|
631
649
|
await sendCommandEvents(cfg, job.commandId, [{
|
|
@@ -719,10 +737,12 @@ async function checkRunningJob(cfg, state) {
|
|
|
719
737
|
return;
|
|
720
738
|
}
|
|
721
739
|
}
|
|
722
|
-
// タイムアウト
|
|
723
|
-
|
|
740
|
+
// タイムアウト — 中央の上限(3時間)で強制停止する。
|
|
741
|
+
// ★RELAY_PENDING_TIMEOUT_MS(15分)は tmux の pending 解除用。画面なし方式では
|
|
742
|
+
// 作業中の職人そのものを殺すので、中央と同じ CENTRAL_BUSY_HOLD_MAX_MS を使う。
|
|
743
|
+
if (now - job.startedAt > CENTRAL_BUSY_HOLD_MAX_MS) {
|
|
724
744
|
log(`WARN site=${cfg.site} id=${job.commandId} タイムアウト(${Math.round((now - job.startedAt) / 1000)}s) → SIGTERM`);
|
|
725
|
-
job.
|
|
745
|
+
job.timedOut = true;
|
|
726
746
|
await stopJob(job);
|
|
727
747
|
return;
|
|
728
748
|
}
|
package/package.json
CHANGED
|
@@ -131,23 +131,43 @@ note() { printf ' %s\n' "$1"; }
|
|
|
131
131
|
SUMMARY=()
|
|
132
132
|
record() { SUMMARY+=("$1"); }
|
|
133
133
|
|
|
134
|
+
# ─── 端末への出力 ──────────────────────────────────────────────────────────
|
|
135
|
+
# 【なぜ /dev/tty へ書くか(実機 2026-10-01・後藤さん PC)】
|
|
136
|
+
# `curl … | bash` を Windows Terminal + WSL で流すと、標準エラー(>&2)に書いた文が
|
|
137
|
+
# 画面に出ない(halt の案内・ask_tty の質問文・die の案内が見えず、合言葉が漏えいした)。
|
|
138
|
+
# 標準出力と sudo(端末へ直接書く)は見えていた。
|
|
139
|
+
# =お客さまが読む必要のある文は /dev/tty(端末そのもの)へ書く。
|
|
140
|
+
#
|
|
141
|
+
# 【検査での差し替え口(CONDUCTOR_INSTALL_TTY_OUT)】
|
|
142
|
+
# 既定は /dev/tty。検査ではふつうのファイルに向け、書かれた内容を確かめる。
|
|
143
|
+
# 入力の差し替え口(CONDUCTOR_INSTALL_TTY)とは別。同じファイルに向けると入力を壊す。
|
|
144
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
145
|
+
INSTALL_TTY_OUT="${CONDUCTOR_INSTALL_TTY_OUT:-/dev/tty}"
|
|
146
|
+
# ★`exec 4>... 2>/dev/null` と書くと exec が stderr も /dev/null に永続的に向けてしまう。
|
|
147
|
+
# 先に書けるかを調べ(サブシェルで試す)、開けるときだけ exec する。
|
|
148
|
+
if (exec 3>"$INSTALL_TTY_OUT") 2>/dev/null; then
|
|
149
|
+
exec 4>"$INSTALL_TTY_OUT"
|
|
150
|
+
else
|
|
151
|
+
exec 4>&2 # 端末が開けなければ標準エラーへ落とす
|
|
152
|
+
fi
|
|
153
|
+
|
|
134
154
|
# 直せないので止まる(お客さまの手番)。★必ず「何をして、次にどうするか」を書く。
|
|
135
155
|
halt() {
|
|
136
|
-
printf '\n%s\n' "$BAR" >&
|
|
137
|
-
printf 'ここでいったん止まります(あと1つ、お手数をおかけします)\n' >&
|
|
138
|
-
printf '%s\n\n' "$BAR" >&
|
|
139
|
-
printf '%s\n' "$1" >&
|
|
140
|
-
printf '\n%s\n' "$BAR" >&
|
|
141
|
-
printf 'この作業が終わったら、さきほどの1行をもう一度そのまま打ってください。\n' >&
|
|
142
|
-
printf '済んだところは飛ばして、続きから進みます。\n' >&
|
|
143
|
-
printf '%s\n' "$BAR" >&
|
|
156
|
+
printf '\n%s\n' "$BAR" >&4
|
|
157
|
+
printf 'ここでいったん止まります(あと1つ、お手数をおかけします)\n' >&4
|
|
158
|
+
printf '%s\n\n' "$BAR" >&4
|
|
159
|
+
printf '%s\n' "$1" >&4
|
|
160
|
+
printf '\n%s\n' "$BAR" >&4
|
|
161
|
+
printf 'この作業が終わったら、さきほどの1行をもう一度そのまま打ってください。\n' >&4
|
|
162
|
+
printf '済んだところは飛ばして、続きから進みます。\n' >&4
|
|
163
|
+
printf '%s\n' "$BAR" >&4
|
|
144
164
|
exit 1
|
|
145
165
|
}
|
|
146
166
|
|
|
147
167
|
# 想定していない壊れ方(お客さまには直せないので、そのまま送ってもらう)。
|
|
148
168
|
die() {
|
|
149
|
-
printf '\n[install] うまくいきませんでした: %s\n' "$1" >&
|
|
150
|
-
printf '[install] この画面をそのままサポートへお送りください。\n' >&
|
|
169
|
+
printf '\n[install] うまくいきませんでした: %s\n' "$1" >&4
|
|
170
|
+
printf '[install] この画面をそのままサポートへお送りください。\n' >&4
|
|
151
171
|
exit 1
|
|
152
172
|
}
|
|
153
173
|
|
|
@@ -168,10 +188,10 @@ die() {
|
|
|
168
188
|
# 止まった(CI は端末が無いので気づけなかった=すり抜けた)。
|
|
169
189
|
# ★入力元を差し替えられるようにするだけ。段・判定・止まり方は1つも変えない。
|
|
170
190
|
#
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
191
|
+
# 【聞く文は端末(fd 4)へ出す】
|
|
192
|
+
# 以前は標準エラー(>&2)へ出していたが、`curl … | bash` を Windows Terminal + WSL で
|
|
193
|
+
# 流すと標準エラーが画面に出ない(実機 2026-10-01・後藤さん PC)。
|
|
194
|
+
# 端末(/dev/tty・検査時は CONDUCTOR_INSTALL_TTY_OUT)へ書く。
|
|
175
195
|
# >>> tty-io >>> ★この印の間はテストが切り出して単体で流す。動かすときは消さない。
|
|
176
196
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
177
197
|
|
|
@@ -184,10 +204,10 @@ tty_open() {
|
|
|
184
204
|
return 0
|
|
185
205
|
fi
|
|
186
206
|
if ! exec 3<"$INSTALL_TTY" 2>/dev/null; then
|
|
187
|
-
printf '\n[install] ここから先は、お客さまに入力していただく必要があります。\n' >&
|
|
188
|
-
printf '[install] ★いまの実行には「入力できる画面(端末)」がありません。\n' >&
|
|
189
|
-
printf '[install] 自動実行や、画面のない環境からは進められません。\n' >&
|
|
190
|
-
printf '[install] Ubuntu(WSL)の画面をご自分で開いて、そこで1行を打ち直してください。\n' >&
|
|
207
|
+
printf '\n[install] ここから先は、お客さまに入力していただく必要があります。\n' >&4
|
|
208
|
+
printf '[install] ★いまの実行には「入力できる画面(端末)」がありません。\n' >&4
|
|
209
|
+
printf '[install] 自動実行や、画面のない環境からは進められません。\n' >&4
|
|
210
|
+
printf '[install] Ubuntu(WSL)の画面をご自分で開いて、そこで1行を打ち直してください。\n' >&4
|
|
191
211
|
exit 1
|
|
192
212
|
fi
|
|
193
213
|
TTY_READY=1
|
|
@@ -198,11 +218,11 @@ ask_tty() {
|
|
|
198
218
|
local var="$1" prompt="$2" default="${3:-}" answer=''
|
|
199
219
|
tty_open
|
|
200
220
|
if [[ -n "$default" ]]; then
|
|
201
|
-
printf '%s\n' "$prompt" >&
|
|
202
|
-
printf ' (そのままで良ければ Enter → %s): ' "$default" >&
|
|
221
|
+
printf '%s\n' "$prompt" >&4
|
|
222
|
+
printf ' (そのままで良ければ Enter → %s): ' "$default" >&4
|
|
203
223
|
else
|
|
204
|
-
printf '%s\n' "$prompt" >&
|
|
205
|
-
printf ' : ' >&
|
|
224
|
+
printf '%s\n' "$prompt" >&4
|
|
225
|
+
printf ' : ' >&4
|
|
206
226
|
fi
|
|
207
227
|
IFS= read -r -u 3 answer || answer=''
|
|
208
228
|
if [[ -z "$answer" ]]; then
|
|
@@ -215,10 +235,10 @@ ask_tty() {
|
|
|
215
235
|
ask_tty_secret() {
|
|
216
236
|
local var="$1" prompt="$2" answer=''
|
|
217
237
|
tty_open
|
|
218
|
-
printf '%s\n' "$prompt" >&
|
|
219
|
-
printf ' (入力しても画面には出ません): ' >&
|
|
238
|
+
printf '%s\n' "$prompt" >&4
|
|
239
|
+
printf ' (入力しても画面には出ません): ' >&4
|
|
220
240
|
IFS= read -r -s -u 3 answer || answer=''
|
|
221
|
-
printf '\n' >&
|
|
241
|
+
printf '\n' >&4
|
|
222
242
|
printf -v "$var" '%s' "$answer"
|
|
223
243
|
}
|
|
224
244
|
# <<< tty-io <<<
|
|
@@ -988,9 +1008,11 @@ else
|
|
|
988
1008
|
|
|
989
1009
|
ask_tty CONSOLE_URL ' ① 管制のアドレス' "$DEFAULT_CONSOLE_URL"
|
|
990
1010
|
[[ -n "$CONSOLE_URL" ]] || halt '管制のアドレスが空でした。もう一度この1行を打ってやり直してください。'
|
|
1011
|
+
# ★合言葉をアドレス欄に入れてしまった場合の防止(実機 2026-10-01: 合言葉が画面に表示されて漏えい)。
|
|
1012
|
+
# 入力値は画面にもログにも出さない(合言葉そのものの可能性があるため)。
|
|
991
1013
|
case "$CONSOLE_URL" in
|
|
992
1014
|
https://*) : ;;
|
|
993
|
-
*) halt
|
|
1015
|
+
*) CONSOLE_URL=''; halt 'https:// で始まっていません。合言葉はまだ入れないでください。次の欄(②)が合言葉です。もう一度この1行を打ってやり直してください。' ;;
|
|
994
1016
|
esac
|
|
995
1017
|
|
|
996
1018
|
ask_tty_secret CONSOLE_TOKEN ' ② 合言葉'
|
|
@@ -1112,7 +1134,8 @@ if [[ "$DRY_RUN" == "1" ]]; then
|
|
|
1112
1134
|
else
|
|
1113
1135
|
doing '采配くんを設置します(ここから先は設置係が引き継ぎます)'
|
|
1114
1136
|
say ''
|
|
1115
|
-
|
|
1137
|
+
# ★画面なし方式であることを設置係へ伝える(⑧の案内を切り替えるため)。
|
|
1138
|
+
CONDUCTOR_RUNNER_MODE=headless conductor setup "$SITE" || die '設置に失敗しました(上に出ている内容をサポートへお送りください)'
|
|
1116
1139
|
record '設置: 実行した'
|
|
1117
1140
|
fi
|
|
1118
1141
|
|
package/sales-template/setup.sh
CHANGED
|
@@ -105,8 +105,21 @@ PROJECT_DIR="${2:-$HOME/projects/$SITE}"
|
|
|
105
105
|
MIN_CONDUCTOR_VERSION="0.12.3"
|
|
106
106
|
|
|
107
107
|
# ─── ヘルパ ───────────────────────────────────────────────────────────────────
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
|
|
109
|
+
# ─── 端末への出力(install.sh と同じ仕組み) ─────────────────────────────────
|
|
110
|
+
# 【なぜ /dev/tty へ書くか】`curl … | bash` を Windows Terminal + WSL で流すと、
|
|
111
|
+
# 標準エラー(>&2)に書いた文が画面に出ない(実機 2026-10-01)。
|
|
112
|
+
# お客さまが読む文は端末そのもの(/dev/tty)へ書く。
|
|
113
|
+
_SETUP_TTY_OUT="${CONDUCTOR_INSTALL_TTY_OUT:-/dev/tty}"
|
|
114
|
+
# ★`exec 9>... 2>/dev/null` と書くと exec が stderr も /dev/null に永続的に向けてしまう。
|
|
115
|
+
if (exec 3>"$_SETUP_TTY_OUT") 2>/dev/null; then
|
|
116
|
+
exec 9>"$_SETUP_TTY_OUT"
|
|
117
|
+
else
|
|
118
|
+
exec 9>&2 # 端末が開けなければ標準エラーへ落とす
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
fail() { echo >&9; echo "[setup] エラー: $1" >&9; exit 1; }
|
|
122
|
+
warn() { echo "[setup] warn: $1" >&9; }
|
|
110
123
|
|
|
111
124
|
# 入力元。curl | bash では /dev/tty から読む。テスト等で端末が無い場合は標準入力に落ちる。
|
|
112
125
|
# install.sh の CONDUCTOR_INSTALL_TTY と同じ差し替え口を使う。
|
|
@@ -121,15 +134,18 @@ fi
|
|
|
121
134
|
|
|
122
135
|
# setup_read — /dev/tty があれば使い、無ければ標準入力から読む。
|
|
123
136
|
# 使い方: setup_read [-s] <変数名> <プロンプト>
|
|
137
|
+
# ★プロンプト(聞く文)は端末(fd 9)へ出す。`read -p` は stderr に書くが、
|
|
138
|
+
# Windows Terminal + WSL では見えないので、先に fd 9 へ書いてから `-p ''` で読む。
|
|
124
139
|
setup_read() {
|
|
125
140
|
local silent='' var prompt
|
|
126
141
|
if [[ "${1:-}" == "-s" ]]; then silent='-s'; shift; fi
|
|
127
142
|
var="$1"; prompt="$2"
|
|
143
|
+
printf '%s' "$prompt" >&9
|
|
128
144
|
# shellcheck disable=SC2229 # $silent は動的な旗、$var は動的な変数名。意図どおり。
|
|
129
145
|
if [[ "$_SETUP_TTY_OK" == "1" ]]; then
|
|
130
|
-
IFS= read -r $silent
|
|
146
|
+
IFS= read -r $silent "$var" <"$_SETUP_TTY" || printf -v "$var" ''
|
|
131
147
|
else
|
|
132
|
-
IFS= read -r $silent
|
|
148
|
+
IFS= read -r $silent "$var" || printf -v "$var" ''
|
|
133
149
|
fi
|
|
134
150
|
}
|
|
135
151
|
|
|
@@ -378,9 +394,9 @@ fi
|
|
|
378
394
|
|
|
379
395
|
if [[ ${#errors[@]} -gt 0 ]]; then
|
|
380
396
|
echo
|
|
381
|
-
echo "[setup] 前提チェックに失敗しました:" >&
|
|
397
|
+
echo "[setup] 前提チェックに失敗しました:" >&9
|
|
382
398
|
for e in "${errors[@]}"; do
|
|
383
|
-
echo " - $e" >&
|
|
399
|
+
echo " - $e" >&9
|
|
384
400
|
done
|
|
385
401
|
exit 1
|
|
386
402
|
fi
|
|
@@ -1181,7 +1197,47 @@ fi
|
|
|
1181
1197
|
|
|
1182
1198
|
step "⑧ 完了! 職人(Claude Code)の起動方法"
|
|
1183
1199
|
|
|
1184
|
-
|
|
1200
|
+
if [[ "${CONDUCTOR_RUNNER_MODE:-}" == "headless" ]]; then
|
|
1201
|
+
# ★画面なし方式(install.sh から呼ばれた場合)。tmux の3行は出さない。
|
|
1202
|
+
cat <<GUIDE
|
|
1203
|
+
|
|
1204
|
+
采配くんのセットアップが完了しました。
|
|
1205
|
+
|
|
1206
|
+
管制から便を出すと自動で動きます。
|
|
1207
|
+
職人の画面を手で開く必要はありません。
|
|
1208
|
+
|
|
1209
|
+
──────────────────────────────────────────────────────────
|
|
1210
|
+
★いちばん確実な「動いています」の確かめ方
|
|
1211
|
+
──────────────────────────────────────────────────────────
|
|
1212
|
+
|
|
1213
|
+
管制の画面(采配くん)をブラウザで開いてください。
|
|
1214
|
+
そこに「${SITE}」が出ていれば、つながって動いています。★これが正の確認方法です。
|
|
1215
|
+
|
|
1216
|
+
出てこないときは、次の順に試してください。
|
|
1217
|
+
1. 1〜2分待ってから、管制の画面を開き直す
|
|
1218
|
+
2. それでも出ないときは: ${SVC_RESTART_CMD}
|
|
1219
|
+
3. それでも出ないときは、この画面をそのままサポートへお送りください
|
|
1220
|
+
|
|
1221
|
+
──────────────────────────────────────────────────────────
|
|
1222
|
+
困ったとき(黒い画面のコマンド・補助)
|
|
1223
|
+
──────────────────────────────────────────────────────────
|
|
1224
|
+
状態を見る: ${SVC_STATUS_CMD}
|
|
1225
|
+
動きの記録: ${SVC_LOG_CMD}
|
|
1226
|
+
入れ直す: ${SVC_RESTART_CMD}
|
|
1227
|
+
止める: ${SVC_STOP_CMD}
|
|
1228
|
+
合言葉を入れ直す: rm ~/.conductor.env → そのあと setup.sh をもう一度実行
|
|
1229
|
+
|
|
1230
|
+
──────────────────────────────────────────────────────────
|
|
1231
|
+
職人の画面を手で開きたいとき(tmux 方式・補助)
|
|
1232
|
+
──────────────────────────────────────────────────────────
|
|
1233
|
+
|
|
1234
|
+
tmux new-session -d -s "$SITE" -c "$PROJECT_DIR"
|
|
1235
|
+
tmux send-keys -t "$SITE" "unset ANTHROPIC_API_KEY && claude" C-m
|
|
1236
|
+
tmux attach -t "$SITE"
|
|
1237
|
+
|
|
1238
|
+
GUIDE
|
|
1239
|
+
else
|
|
1240
|
+
cat <<GUIDE
|
|
1185
1241
|
|
|
1186
1242
|
采配くんのセットアップが完了しました。
|
|
1187
1243
|
|
|
@@ -1219,3 +1275,4 @@ cat <<GUIDE
|
|
|
1219
1275
|
合言葉を入れ直す: rm ~/.conductor.env → そのあと setup.sh をもう一度実行
|
|
1220
1276
|
|
|
1221
1277
|
GUIDE
|
|
1278
|
+
fi
|