import fs from "node:fs"; import path from "node:path"; import readline from "node:readline"; import { stdin as input, stdout as output } from "node:process"; import { PHASE0_DIR } from "./paths"; export const TRIGGER_CHECKPOINT_A = path.join(PHASE0_DIR, "trigger-checkpoint-a"); export const TRIGGER_CHECKPOINT_B = path.join(PHASE0_DIR, "trigger-checkpoint-b"); export const RECORDING_STATUS = path.join(PHASE0_DIR, "recording.status.json"); const POLL_MS = 400; function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } function consumeTrigger(triggerPath: string): void { if (fs.existsSync(triggerPath)) { fs.rmSync(triggerPath, { force: true }); } } async function waitForTriggerFile( triggerPath: string, label: string, ): Promise { consumeTrigger(triggerPath); const fileName = path.basename(triggerPath); console.log(` [phase0] ${label} — 文件触发模式 在项目根目录另开终端执行: PowerShell: New-Item -ItemType File -Force -Path ".rpa\\phase0\\${fileName}" CMD: type nul > .rpa\\phase0\\${fileName} 等待文件: ${triggerPath} `); while (!fs.existsSync(triggerPath)) { await sleep(POLL_MS); } consumeTrigger(triggerPath); console.log(`[phase0] ${label} 已触发 (${fileName})`); } export function writeRecordingStatus(payload: Record): void { fs.mkdirSync(PHASE0_DIR, { recursive: true }); fs.writeFileSync( RECORDING_STATUS, `${JSON.stringify({ updatedAt: new Date().toISOString(), ...payload }, null, 2)}\n`, "utf8", ); } /** * 等待人工确认。优先 TTY Enter;非 TTY 或 PHASE0_USE_FILE_TRIGGER=1 时用触发文件。 */ export async function waitForCheckpoint(options: { label: string; message: string; triggerPath: string; rl?: readline.Interface | null; }): Promise { const useFileTrigger = process.env.PHASE0_USE_FILE_TRIGGER === "1" || !process.stdin.isTTY; console.log(`\n[phase0] ── ${options.label} ──`); console.log(options.message); if (useFileTrigger) { await waitForTriggerFile(options.triggerPath, options.label); return; } const rl = options.rl ?? readline.createInterface({ input, output, terminal: true }); await new Promise((resolve) => { rl.question("\n按 Enter 继续… ", () => { resolve(); }); }); if (!options.rl) { rl.close(); } } export function createSharedReadline(): readline.Interface { return readline.createInterface({ input, output, terminal: true }); }