You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

91 lines
2.5 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<void> {
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<void> {
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<string, unknown>): 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<void> {
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<void>((resolve) => {
rl.question("\n按 Enter 继续… ", () => {
resolve();
});
});
if (!options.rl) {
rl.close();
}
}
export function createSharedReadline(): readline.Interface {
return readline.createInterface({ input, output, terminal: true });
}