walover-line-harness-gui 0.6.0 → 0.7.0
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/server/index.js +4 -1
- package/src/server/runner.js +76 -1
- package/src/web/index.html +24 -0
package/package.json
CHANGED
package/src/server/index.js
CHANGED
|
@@ -213,7 +213,10 @@ async function route(req, res, pathname, method) {
|
|
|
213
213
|
}
|
|
214
214
|
const body = await readJson(req);
|
|
215
215
|
try {
|
|
216
|
-
if (body?.manual === true) {
|
|
216
|
+
if (body?.manual === true && typeof body.key === "string") {
|
|
217
|
+
// 決まったキーだけ。**画面から任意の列を送らせない**(#59)
|
|
218
|
+
run.sendKey(body.key);
|
|
219
|
+
} else if (body?.manual === true) {
|
|
217
220
|
// 止まっているときに、画面を見た人が自分で打つ逃げ道
|
|
218
221
|
run.answerManually(String(body.text ?? ""));
|
|
219
222
|
} else {
|
package/src/server/runner.js
CHANGED
|
@@ -25,6 +25,9 @@ const KEY_ENTER = "\r";
|
|
|
25
25
|
const KEY_DOWN = "\x1b[B";
|
|
26
26
|
const KEY_UP = "\x1b[A";
|
|
27
27
|
|
|
28
|
+
/** 止まったときに画面から送れるキー。**ここに無いものは送れない** */
|
|
29
|
+
const MANUAL_KEYS = { up: KEY_UP, down: KEY_DOWN, enter: KEY_ENTER };
|
|
30
|
+
|
|
28
31
|
/** 画面が落ち着くまでの待ち時間。clack は1文字ごとに描き直すため、まとめてから判定する */
|
|
29
32
|
const QUIET_MS = 80;
|
|
30
33
|
|
|
@@ -104,6 +107,8 @@ export class SetupRun extends EventEmitter {
|
|
|
104
107
|
this.halt = null;
|
|
105
108
|
/** 無反応を見張る時計。出力が来なくても判定を動かす @type {NodeJS.Timeout|null} */
|
|
106
109
|
this.watchdog = null;
|
|
110
|
+
/** 人が矢印で選び直している最中か。出力が来ても停止表示を消さない */
|
|
111
|
+
this.manualHold = false;
|
|
107
112
|
|
|
108
113
|
/** 直近に検出した枠 @type {import("./prompt-detector.js").DetectedPrompt|null} */
|
|
109
114
|
this.detected = null;
|
|
@@ -216,6 +221,15 @@ export class SetupRun extends EventEmitter {
|
|
|
216
221
|
this.exit = info;
|
|
217
222
|
this.status = "finished";
|
|
218
223
|
if (this.watchdog) clearTimeout(this.watchdog);
|
|
224
|
+
|
|
225
|
+
// 「画面が止まっています」は、終わってしまえば意味を持たない。
|
|
226
|
+
//
|
|
227
|
+
// Windows は pty の終了通知が遅れるので、最後の出力のあと・終了の前に
|
|
228
|
+
// 見張りタイマーが発火しうる。それを終了後まで持ち越すと、
|
|
229
|
+
// 完走したのに「止まっています」が残る(CI で実際に出た)。
|
|
230
|
+
//
|
|
231
|
+
// 他の理由(未知のプロンプトなど)は残す。なぜ止めたかは終わったあとも意味がある
|
|
232
|
+
if (this.halt?.reason === "no-prompt-stalled") this.halt = null;
|
|
219
233
|
// 仕上げの手作業に使う値を取り出す。読めなければ端末表示のままにする
|
|
220
234
|
this.completion = parseCompletion(this.term.snapshot(), {
|
|
221
235
|
// 画面から読めなくても、フォームに入っていた値で補える
|
|
@@ -228,6 +242,19 @@ export class SetupRun extends EventEmitter {
|
|
|
228
242
|
}
|
|
229
243
|
|
|
230
244
|
#onData(chunk) {
|
|
245
|
+
// 「画面が止まっている」という判断は、**出力が来た時点で覆る**。
|
|
246
|
+
//
|
|
247
|
+
// 沈黙だけを根拠にしているので、動き出したら取り消すのが筋。
|
|
248
|
+
// 取り消さないと、本家が黙って作業していただけの場面で
|
|
249
|
+
// 「止まっています」が居座る(Windows の遅い pty で実際に出た)。
|
|
250
|
+
//
|
|
251
|
+
// ただし人が矢印で選び直している最中は消さない。
|
|
252
|
+
// 選択肢が動くたびに出力が来るので、押すボタンが消えてしまう
|
|
253
|
+
if (this.halt?.reason === "no-prompt-stalled" && !this.manualHold) {
|
|
254
|
+
this.halt = null;
|
|
255
|
+
this.status = "running";
|
|
256
|
+
}
|
|
257
|
+
|
|
231
258
|
// **出力が来ている = 本家は動いている。**
|
|
232
259
|
//
|
|
233
260
|
// 進捗を「こちらが答えたとき」だけで測っていたため、本家が長く作業すると
|
|
@@ -291,7 +318,26 @@ export class SetupRun extends EventEmitter {
|
|
|
291
318
|
this.detectedFrameId = detected ? this.#frameId(detected) : null;
|
|
292
319
|
|
|
293
320
|
if (!detected || !detected.complete) {
|
|
294
|
-
// プロンプトが出ていない =
|
|
321
|
+
// プロンプトが出ていない = 本家が作業中……とは限らない。
|
|
322
|
+
//
|
|
323
|
+
// **本家が呼ぶ外部コマンドは、こちらが知らない形で対話を求めてくる。**
|
|
324
|
+
// 実測では wrangler がこれを出し、画面は生きているのに操作できなくなった。
|
|
325
|
+
//
|
|
326
|
+
// ? The project you specified does not exist ... Would you like to create it? ›
|
|
327
|
+
// ❯ Create a new project
|
|
328
|
+
//
|
|
329
|
+
// clack の枠しか読めない以上、**形は分からなくても「止まっている」ことは分かる**
|
|
330
|
+
// 出力が途切れたまま動かないなら、黙って待たずに人へ渡す(#59)。
|
|
331
|
+
if (this.status === "running" && Date.now() - this.progressedAt > this.stuckMs) {
|
|
332
|
+
this.#haltWith(
|
|
333
|
+
"no-prompt-stalled",
|
|
334
|
+
"画面が止まっています",
|
|
335
|
+
"本家のツールが何か入力を待っているようですが、このツールでは形が分かりません。" +
|
|
336
|
+
"下の画面を見て、必要なものを送ってください。",
|
|
337
|
+
null,
|
|
338
|
+
);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
295
341
|
this.spec = null;
|
|
296
342
|
this.pending = null;
|
|
297
343
|
if (this.status !== "running") this.status = "running";
|
|
@@ -422,6 +468,7 @@ export class SetupRun extends EventEmitter {
|
|
|
422
468
|
this.status = "halted";
|
|
423
469
|
this.pending = null;
|
|
424
470
|
this.pendingSelect = null;
|
|
471
|
+
this.manualHold = false;
|
|
425
472
|
this.halt = {
|
|
426
473
|
reason,
|
|
427
474
|
message,
|
|
@@ -571,6 +618,34 @@ export class SetupRun extends EventEmitter {
|
|
|
571
618
|
*
|
|
572
619
|
* @param {string} text
|
|
573
620
|
*/
|
|
621
|
+
/**
|
|
622
|
+
* 止まっているときに、決まったキーだけ送る。
|
|
623
|
+
*
|
|
624
|
+
* 選択肢を動かすには矢印キーが要るが、**画面から任意の文字列を送らせない**。
|
|
625
|
+
* ここに無いものは送れない(`\x1b[A` のような列を本文で受け取らない)。
|
|
626
|
+
*/
|
|
627
|
+
sendKey(name) {
|
|
628
|
+
if (this.status !== "halted") throw new Error("止まっているときだけ送れます");
|
|
629
|
+
const key = MANUAL_KEYS[name];
|
|
630
|
+
if (!key) throw new Error("送れないキーです");
|
|
631
|
+
|
|
632
|
+
this.progressedAt = Date.now();
|
|
633
|
+
this.pty?.write(key);
|
|
634
|
+
|
|
635
|
+
// 矢印は「選び直している」だけなので、止まったままにする。
|
|
636
|
+
// 決定(Enter)で初めて先へ進む
|
|
637
|
+
if (name !== "enter") {
|
|
638
|
+
// 選択肢が動くと出力が来る。それで停止表示を消さない
|
|
639
|
+
this.manualHold = true;
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
this.manualHold = false;
|
|
644
|
+
this.halt = null;
|
|
645
|
+
this.status = "running";
|
|
646
|
+
if (this.detected) this.answeredFrames.add(this.#frameId(this.detected));
|
|
647
|
+
}
|
|
648
|
+
|
|
574
649
|
answerManually(text) {
|
|
575
650
|
if (this.status !== "halted") throw new Error("止まっているときだけ手入力できます");
|
|
576
651
|
// 人が答えたのも進捗。ここを忘れると、復帰した直後に同じ判定へ落ちる
|
package/src/web/index.html
CHANGED
|
@@ -772,11 +772,35 @@
|
|
|
772
772
|
el("p", { textContent: "下の画面を見て、必要であれば自分で入力してください。" }),
|
|
773
773
|
);
|
|
774
774
|
|
|
775
|
+
// **キーを押せるようにする。**
|
|
776
|
+
//
|
|
777
|
+
// 本家が呼ぶ外部コマンドは、矢印で選んで Enter で決める形の対話を出すことがある。
|
|
778
|
+
// 実測では wrangler がそれを出し、画面は生きているのに操作できなかった(#59)。
|
|
779
|
+
// 文字を打つ欄だけでは、そこから抜けられない。
|
|
780
|
+
box.append(
|
|
781
|
+
el("p", {
|
|
782
|
+
className: "cli-echo",
|
|
783
|
+
textContent:
|
|
784
|
+
"下の黒い枠が選択肢を出している場合は、↑ ↓ で選んでから「Enter を送る」を押してください。",
|
|
785
|
+
}),
|
|
786
|
+
);
|
|
787
|
+
const keys = el("div", { className: "choices" });
|
|
788
|
+
for (const [label, key] of [["↑", "up"], ["↓", "down"]]) {
|
|
789
|
+
const button = el("button", { type: "button", textContent: label });
|
|
790
|
+
button.addEventListener("click", () => answer({ manual: true, key }));
|
|
791
|
+
keys.append(button);
|
|
792
|
+
}
|
|
793
|
+
const enter = el("button", { className: "go", type: "button", textContent: "Enter を送る" });
|
|
794
|
+
enter.addEventListener("click", () => answer({ manual: true, key: "enter" }));
|
|
795
|
+
keys.append(enter);
|
|
796
|
+
box.append(keys);
|
|
797
|
+
|
|
775
798
|
const input = el("input", { type: "text", autocomplete: "off" });
|
|
776
799
|
const go = el("button", { className: "go", type: "button", textContent: "送信" });
|
|
777
800
|
go.addEventListener("click", () =>
|
|
778
801
|
answer({ manual: true, text: input.value }).then(() => (input.value = "")),
|
|
779
802
|
);
|
|
803
|
+
box.append(el("p", { className: "cli-echo", textContent: "文字を打つ必要がある場合はこちら:" }));
|
|
780
804
|
box.append(el("div", { className: "row" }, [el("div", { className: "row" }, [input])]));
|
|
781
805
|
// 打つ内容が分からないこともある。そのときのために、やり直す道も並べる
|
|
782
806
|
box.append(el("div", { className: "choices" }, [go, rerunButton("中止して最初からやり直す")]));
|