/** pi 侧扩展写入的状态文件(运行时数据,编译期不依赖其存在)。 */ export declare const PI_SESSION_STATE_FILE = "pi-session-state.json"; /** watchdog 恢复日志(追加行,JSON per line)。 */ export declare const PI_AUTO_RECOVER_LOG = "pi-auto-recover.log"; /** 默认证据目录(watchdog 运行时证据,落在 tmp 域避免污染 .aiws/changes/;可用 --evidence-dir 覆盖)。 */ export declare const DEFAULT_EVIDENCE_REL_DIR: string; export declare function piSessionStatePath(root: string): string; export declare function piAutoRecoverLogPath(root: string, target?: string): string; export declare function defaultEvidenceDir(root: string): string; export type PiSessionStateValue = "running" | "failed_upstream" | "completed" | "idle"; export interface PiSessionState { state: PiSessionStateValue; /** pi 内置 retry 已重试次数 */ retryCount?: number; /** 上游失败原因(尽量带 HTTP status / 错误信息) */ failureReason?: string; /** 状态写入时间戳(ISO 或 epoch 字符串) */ ts?: string; /** pi 进程 pid */ pid?: number; /** 可选:pi 会话所在 tmux 会话名(扩展写入时 watchdog 优先采用) */ session?: string; } /** 解析状态文件内容;损坏 JSON / 非法 state 抛 UserError(由调用方告警并跳过本轮)。 */ export declare function parsePiSessionState(raw: string): PiSessionState; /** 读状态文件;缺失返回 null,损坏抛 UserError。 */ export declare function readPiSessionState(root: string): Promise; /** 分类判定:仅 failed_upstream 触发恢复;completed/idle/running 绝不打扰。 */ export declare function isRecoverableState(state: PiSessionState | null): boolean; /** 状态文件 ts → epoch ms;缺失/非法返回 0。 */ export declare function stateTsMs(state: PiSessionState): number; export interface AutoRecoverConfig { /** 是否启用自动恢复(默认 true) */ enabled?: boolean; /** 恢复触发冷却秒数(默认 60;0 表示不冷却) */ cooldownSecs?: number; /** 自动恢复次数上限(默认 5;达到后输出诊断并停止) */ maxRecoveries?: number; /** 目标 tmux 会话名(默认探测:状态文件 session 字段 / 当前 TMUX 环境 / pane 命令为 pi 的会话) */ tmuxSession?: string; } export interface ResolvedAutoRecoverConfig { enabled: boolean; cooldownSecs: number; maxRecoveries: number; tmuxSession?: string; } export declare const DEFAULT_AUTO_RECOVER_CONFIG: ResolvedAutoRecoverConfig; export declare function resolveAutoRecoverConfig(cfg: { autoRecover?: AutoRecoverConfig; } | null | undefined): ResolvedAutoRecoverConfig; /** 从 .aiws/config.json 读取 autoRecover 配置;缺文件/缺字段/配置损坏均用安全默认值。 */ export declare function loadAutoRecoverConfig(root: string): Promise; export interface RecoveryLogEntry { /** 恢复动作 ISO 时间戳 */ ts: string; /** 恢复动作 epoch ms(用于冷却判定) */ tsMs: number; /** 累计恢复次数(1 起) */ count: number; /** 触发时的状态值(恒 failed_upstream) */ state: PiSessionStateValue; /** 状态文件 ts */ stateTs?: string; /** 失败原因 */ reason?: string; /** 目标 tmux 会话 */ session?: string; /** 目标(pane id %N 或会话名;pane 检测路径 per-target 日志用) */ target?: string; /** pi pid */ pid?: number; /** send-keys 是否成功 */ ok: boolean; } export interface RecoveryMeta { /** 累计恢复次数 */ count: number; /** 最近一次恢复时间(epoch ms;无记录为 0) */ lastRecoveryAt: number; } /** 解析恢复日志:取最后一行 JSON 的 count/tsMs;损坏行降级为按行数计数。 */ export declare function parseRecoveryLog(raw: string): RecoveryMeta; export declare function readRecoveryLog(root: string, target?: string): Promise; export declare function appendRecoveryLog(root: string, entry: RecoveryLogEntry, target?: string): Promise; /** * 冷却判定:now - lastActivityMs >= cooldownSecs 才允许触发。 * lastActivityMs = max(状态文件 ts, 上次恢复时间)——首次失败从失败时刻起冷却, * 后续从上次恢复起冷却(状态文件 ts 未更新也不重复触发)。 */ export declare function isInCooldown(lastActivityMs: number, cooldownSecs: number, nowMs: number): boolean; /** * 上游错误特征(PROB-PI-WATCH-STALL)。非交互 session(pi @file / batch / * review 派发)不会写 pi-session-state.json 的 failed_upstream,watchdog * 依赖状态文件的旧路径无从触发;此正则直接从 tmux pane 文本识别上游错误, * 命中即 send-keys "继续"(pi 多上游会重试切到健康上游)。 */ export declare const UPSTREAM_ERROR_RE: RegExp; /** * 护栏拦截特征(PROB-GUARDRAIL-BLOCK-STALL)。tool_call 被仓库安全护栏拦截后 * agent 停在 Working 运行态不消费消息队列(send-keys 消息仅 follow-up 排队, * 需 Esc(app.interrupt)中止当前操作后才执行)——必须用 Esc + 恢复消息序列。 */ export declare const GUARDRAIL_BLOCK_RE: RegExp; /** 捕获 tmux 会话/pane 文本;目标不存在/非法返回空串。 */ export declare function capturePaneText(session: string): string; /** 列出匹配 --stall-pattern 的 tmux 会话(简单 glob:* / ?)。 */ export declare function listTmuxSessions(pattern?: string): string[]; export interface StallPollOptions { root: string; /** 目标会话名(单一) */ stallSession?: string; /** 会话名 glob(可匹配多个,如 aiws-task-* / review-*) */ stallPattern?: string; cooldownSecs: number; maxRecoveries: number; dryRun?: boolean; evidenceDir?: string; now?: () => number; /** 注入 pane 文本(测试用;缺省由 capturePaneText 实时采集) */ paneText?: (session: string) => string; /** 护栏拦截去重状态:target -> pane 指纹(上次恢复时);缺省不去重(兼容旧行为) */ paneSeen?: Map; } /** * pane 文本指纹:长度 + 尾部 200 字符(新输出会改变尾部,用于判断 agent 是否有进展)。 */ export declare function paneFingerprint(text: string): string; export type StallPollResult = { kind: "no_session"; session: string | undefined; } | { kind: "cooldown"; secondsLeft: number; } | { kind: "healthy"; sessions: string[]; } | { kind: "recovered"; session: string; count: number; ok: boolean; mode: "upstream" | "guardrail"; } | { kind: "exhausted"; count: number; }; /** * 单轮 stall 检查:对目标会话(单一或 pattern 匹配)捕获 pane → 命中 * UPSTREAM_ERROR_RE → 冷却/上限 → send-keys 恢复。不 sleep。 * 可测:paneText 注入时不再触碰真实 tmux(capture 由注入函数承担)。 */ export declare function runStallPollOnce(opts: StallPollOptions): Promise; /** 冻结的恢复消息(Q7)。 */ export declare const RECOVERY_MESSAGE = "\u7EE7\u7EED\u6267\u884C\u521A\u624D\u4E2D\u65AD\u7684\u4EFB\u52A1"; /** * 护栏拦截场景的恢复消息(PROB-GUARDRAIL-BLOCK-STALL)。拦截后 agent 停在 Working 运行态, * 需 Esc 中止 + 明确指引(改用仓库内路径 / allowlist 放行形式),避免重复同一条命令。 */ export declare const GUARDRAIL_RECOVERY_MESSAGE = "\u4F60\u4E0A\u4E00\u6761\u547D\u4EE4\u88AB\u4ED3\u5E93\u5B89\u5168\u62A4\u680F\u62E6\u622A\u4E14\u672A\u6267\u884C\u3002\u8BF7\u91CD\u65B0\u6267\u884C\uFF1A\u547D\u4EE4\u6539\u7528\u4ED3\u5E93\u5185\u8DEF\u5F84\uFF08\u65E5\u5FD7\u5199\u5230 .aiws/ \u6216 tasks/ \u4E0B\uFF09\uFF0C\u6216\u4F7F\u7528 allowlist \u5DF2\u653E\u884C\u7684\u5F62\u5F0F\uFF1B\u4E0D\u8981\u91CD\u590D\u540C\u4E00\u6761\u547D\u4EE4\u3002"; export declare function buildRecoveryMessage(state: PiSessionState, count: number): string; /** 目标会话名安全校验:仅允许 [A-Za-z0-9._-],防止 shell 注入。 */ export declare function isValidTmuxSessionName(name: string): boolean; /** * tmux 操作目标安全校验:会话名([A-Za-z0-9._-])或 pane id(%N,split 分屏模式) * ——guardian 需守护 split pane(PROB-PI-WATCH-GUARDIAN pane 支持)。 */ export declare function isValidTmuxTarget(name: string): boolean; /** 转义发送文本,使其在 tmux send-keys 的双引号上下文中原样输入(对齐 session-spawner-tmux 的 escapeShellDoubleQuoted)。 */ export declare function escapeSendKeys(text: string): string; export declare function tmuxAvailable(): boolean; /** 向指定 tmux 会话/pane 发送文本 + Enter;目标不存在/失败抛错(由调用方记录并继续)。 */ export declare function tmuxSendKeys(session: string, message: string): void; /** * 发送 Esc(pi TUI app.interrupt:中止当前操作)。护栏拦截后 agent 停在 Working 运行态, * 消息仅 follow-up 排队(app.message.followUp alt+enter 语义)不执行;Esc 中止当前操作后 * 队列消息才会释放/可执行。目标非法抛错。 */ /** * 统一恢复序列(D2):Esc 中止当前操作 → 600ms → send-keys 消息。 * agent Working 态直接 send-keys 只排队不执行;空闲态 Esc 无副作用。 */ export declare function tmuxRecoverSequence(session: string, message: string): Promise; export declare function tmuxSendInterrupt(session: string): void; /** * 检查 tmux 会话是否存在(has-session);tmux 不可用/异常返回 false。 * guardian 模式用:目标会话消失后 watchdog 应自动退出,防僵尸进程。 /** * 检查 tmux 目标(会话或 pane id)是否存在;tmux 不可用/异常返回 false。 * pane id(%N):`tmux list-panes -a -F '#{pane_id}'` 检查包含; * 会话名:`tmux has-session`。guardian 模式用:目标消失后 watchdog 自动退出防僵尸。 */ export declare function tmuxTargetExists(target: string): boolean; /** * 检查 tmux 会话是否存在(has-session);tmux 不可用/异常返回 false。 * 保留给 --stall-pattern 的会话列表语义;pane 目标用 tmuxTargetExists。 */ export declare function tmuxSessionExists(session: string): boolean; /** * 探测默认 tmux 目标: * 1. watchdog 运行在 pi 会话内部(TMUX env)时取当前会话名; * 2. tmux ls 探测 pane 当前命令为 pi 的会话(aiws 惯例:pi 交互式 TUI 跑在独立 pane)。 */ export declare function probeDefaultTmuxSession(): string | undefined; /** 每次恢复追加一行到证据目录(.aiws/tmp/pi-auto-recover-evidence/pi-auto-recover-evidence.md)。 */ export declare function appendRecoveryEvidence(root: string, evDir: string, entry: RecoveryLogEntry, sendError?: string): Promise; /** 达到 maxRecoveries 上限时的恢复指引(明确诊断,不静默)。 */ export declare const RECOVERY_GUIDANCE: string; export interface ExhaustedContext { state: PiSessionState; count: number; cooldownSecs: number; maxRecoveries: number; now: number; } /** 写诊断证据文件,返回其内容。 */ export declare function writeExhaustedDiagnosis(root: string, evDir: string, ctx: ExhaustedContext): Promise; export interface WatchPollOptions { root: string; state: PiSessionState | null; cooldownSecs: number; maxRecoveries: number; tmuxSession?: string; dryRun?: boolean; evidenceDir?: string; now?: () => number; } export type WatchPollResult = { kind: "no_state"; } | { kind: "skip_state"; state: PiSessionStateValue; } | { kind: "cooldown"; secondsLeft: number; } | { kind: "no_session"; } | { kind: "recovered"; count: number; session: string | undefined; ok: boolean; } | { kind: "exhausted"; count: number; }; /** 单轮 watchdog 检查:分类 → 上限 → 冷却 → 恢复。不 sleep;不自己读状态文件。 */ export declare function runWatchPollOnce(opts: WatchPollOptions): Promise; export interface PiWatchOptions { /** workspace 根目录(默认 ".") */ targetPath?: string; /** 轮询间隔秒数(默认 5) */ pollSecs?: number; /** 目标 tmux 会话名(默认探测) */ tmuxSession?: string; /** 只执行一轮检查后退出(默认持续轮询) */ once?: boolean; /** 只打印将执行的恢复动作,不真正 send-keys、不落盘 */ dryRun?: boolean; /** 覆盖配置的冷却秒数 */ cooldownSecs?: number; /** 覆盖配置的恢复次数上限 */ maxRecoveries?: number; /** 证据目录覆盖 */ evidenceDir?: string; /** 可注入时钟(测试用) */ now?: () => number; /** 可注入 sleep(测试用) */ sleepMs?: (ms: number) => Promise; /** pane 级检测(--detect-stall):不依赖状态文件,从 tmux pane 文本识别上游错误 */ detectStall?: boolean; /** stall 目标单一会话名(--stall-session) */ stallSession?: string; /** stall 目标会话 glob(--stall-pattern),如 aiws-task-* / review-* */ stallPattern?: string; /** guardian 模式:目标会话连续消失轮数上限,达到后自动退出(默认 12 轮 ≈ 60s @5s);仅限单一 stallSession */ maxIdleRounds?: number; } export declare function piWatchCommand(options?: PiWatchOptions): Promise;