import { TaskEntry } from "./change-dag-validate.js"; import type { SessionSpawner } from "./session-spawner.js"; export declare function classifyTask(text: string | null | undefined): "react" | "spec" | "weak"; /** 任务分类对应的 persona 指令(注入 handoff Dispatch 段)。 */ export declare function personaInstructionFor(mode: "react" | "spec" | "weak"): string; export declare const MAX_TASK_RETRIES = 3; /** * Structured Pi runtime profile for spawned sub-agent sessions. * * Every field is optional. When any of them is provided WITHOUT an explicit * `--cli-command`, the spawner command is assembled as a deterministic * `pi --provider

--model --thinking ` command (only supplied * fields are emitted). An explicit `--cli-command` always wins verbatim and * is never parsed or rewritten (see {@link resolveSpawnCliCommand}). */ export interface PiRuntimeProfile { /** Pi `--provider` name (e.g. anthropic/openai/aipper). */ provider?: string; /** Pi `--model` pattern or ID (supports `provider/id` and `:` shorthand). */ model?: string; /** Pi `--thinking` level (off|minimal|low|medium|high|xhigh|max). */ thinking?: string; } /** Valid `pi --thinking` levels (pi CLI contract: off|minimal|low|medium|high|xhigh|max). */ export declare const PI_THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"]; /** * Validate a structured profile value before it is embedded in a shell * command. Only token-safe characters are accepted (`[A-Za-z0-9_./:@-]`), * so provider/model/thinking can never smuggle shell metacharacters into the * assembled `pi` command. Throws UserError with a precise message. */ export declare function validatePiProfileValue(value: string, flag: string): string; /** * Build the structured Pi CLI command from a runtime profile. * * Emits only the supplied fields in a fixed flag order * (`--provider` → `--model` → `--thinking`) so the composed command is * deterministic and legible in session logs (model identity traceability, * §Design decision 6). Returns undefined for an empty profile — the spawner * then falls back to {@link detectDefaultCliCommand}. */ export declare function buildPiCliCommand(profile: PiRuntimeProfile): string | undefined; /** * 组装 --resume 续跑命令(pi-tmux-resume-continuation L2-b): * `pi --resume [--provider p] [--model m] [--thinking t]`。 * 用同结构化 profile(与初始 spawn 一致)续跑,保证模型/思维一致; * profile 为空时回退 `pi --resume `。session 可为路径或部分 UUID。 */ export declare function buildResumeCommand(session: string, profile: PiRuntimeProfile): string; /** * Resolve the effective spawn CLI command with explicit precedence * (design decision 1: `--cli-command` > structured Pi params > default): * * 1. explicit `--cli-command` — returned verbatim, never parsed or rewritten; * 2. structured Pi profile (provider/model/thinking) — assembled via * {@link buildPiCliCommand} into a `pi` command; * 3. neither — undefined, so the spawner falls back to * {@link detectDefaultCliCommand} (pi-first). */ export declare function resolveSpawnCliCommand(opts: { cliCommand?: string; provider?: string; model?: string; thinking?: string; }): string | undefined; /** * Generate a handoff.md with pre-execution dispatch section for a task. * * When a structured Pi runtime profile is provided (non-empty), a * "## Runtime Profile" section listing the requested provider/model/thinking * is appended so the requested runtime is traceable in the dispatch artifact * (design decision 6). Never records a response/alias model. */ export declare function generateHandoffMd(task: TaskEntry, changeId: string, gitRoot: string, runtimeProfile?: PiRuntimeProfile): string; /** * Generate context.jsonl in delegation-context-injection JSONL format. * * Each line: {"file":"","reason":"","sections":null,"priority":"high|medium|low","kind":"truth|contract|plan|assigned|analysis"} * Read priority order: high → medium → low (skip low when context is tight). */ export declare function generateContextJsonl(task: TaskEntry, changeId: string): string; /** * Generate an evidence/summary.md placeholder for a dispatched task. */ export declare function generateSummaryMd(task: TaskEntry): string; /** * Read tasks.jsonl and return the parsed tasks. */ export declare function readTasksJsonl(jsonlPath: string): Promise<{ tasks: TaskEntry[]; }>; /** * Update a task's status in the tasks.jsonl file by re-parsing and re-serializing. */ export declare function updateTaskStatus(jsonlPath: string, taskId: string, newStatus: TaskEntry["status"]): Promise; /** * Increment a task's retry_count in tasks.jsonl and return the new count. * Task must exist; otherwise throws UserError. */ export declare function incrementTaskRetry(jsonlPath: string, taskId: string): Promise; /** Write the current-work.json entry pointer before dispatching a task. */ export declare function writeCurrentWork(gitRoot: string, entry: { changeId: string; taskId: string; handoffPath: string; evidenceDir: string; doneSignalPath: string; }): Promise; /** Remove the current-work.json pointer after a task completes (or on clear). */ export declare function clearCurrentWork(gitRoot: string): Promise; /** * 解析任务应有的工作目录(子模块并行时指向子模块 worktree)。 * * scope 是逗号分隔的文件路径列表(split-auto 生成形如 `web/src/a.ts`)。 * 若所有文件都属于同一个子模块目录(路径首段为子模块名),返回该子模块目录; * 否则返回 undefined(回退默认 gitRoot,避免在未知目录间跳转)。 * 与 tasksAreParallelSafe 的 .gitmodules 解析一致。 */ export declare function resolveTaskCwd(task: TaskEntry, gitRoot: string, changeDir: string): Promise; /** * 判断一组 ready 任务是否可安全并行(D4 写冲突防护)。 * * 任务 scope 是逗号分隔的文件路径列表(split-auto 生成形如 `web/src/a.ts, backend/src/b.ts`)。 * 判定规则: * - 解析每个任务 scope 中命中不同子模块前缀的文件集合; * - 若两个任务共享同一子模块前缀 → 不安全(会并发写同一子模块 worktree); * - 若某任务 scope 为空或无法解析到子模块 → 保守回退(可能写根仓库共享文件); * - 全部互不相交 → 安全。 */ /** @deprecated 自 M4 起并行门禁收敛至 scopeParallelSafeFor(ADR #22)。保留导出仅为兼容外部调用。 */ export declare function tasksAreParallelSafe(tasks: TaskEntry[], tasksJsonlPath: string): Promise; export interface ParallelDispatchResult { completed: number; dispatched: number; failed: number; total: number; } interface ParallelDispatchOptions { readyTasks: TaskEntry[]; changeId: string; gitRoot: string; changeDir: string; strategy: "tmux" | "l1"; spawner: SessionSpawner; timeoutMs: number; structuredCliCommand: string | undefined; cliCommand: string | undefined; runtimeProfile: PiRuntimeProfile | undefined; tasksJsonlPath: string; parallel: number; } /** * 并行派发:ready 任务全部先写 handoff/context/summary 并 spawn,再用 * spawner.waitAll(handles, { parallel }) 并发等待,最后逐结果收敛 * (completed → status=completed;非 completed/异常 → delegate-failure + retry)。 * current-work.json 仅记录首个任务(单一指针文件无法表达并行)。 */ /** * M3: register an attempt lease before spawning, only when the change uses the * new canonical layout (tasks/runtime.json present). Legacy layouts keep the * old task-level evidence behavior — no attempt dirs, no lease. */ export declare function registerAttemptLeaseIfNewLayout(opts: { changeId: string; taskId: string; gitRoot: string; changeDir: string; taskDir: string; backend: "detached-tmux" | "split-pane" | "l1"; cwd?: string; attemptNo?: number; }): Promise<{ attemptId: string; attemptDir: string; evidenceDir: string; artifactDir: string; } | undefined>; export declare function runParallelDispatch(opts: ParallelDispatchOptions): Promise; /** * Clear stale completion evidence left by a previous dispatch of the same task. * * `evidence/done.signal` is the authoritative completion signal (§3.3.3): its * presence alone means "completed". A stale signal from an earlier run would * therefore make a re-dispatched task look finished before the new session even * starts. We must remove it (and reset the placeholder summary) right before * spawning so only evidence produced by THIS run counts. * * Returns the list of cleared file paths (for logging). */ export declare function clearStaleCompletionEvidence(evidenceDir: string): Promise; /** * Delegate-failure evidence for a non-completed session result. * * Recorded per design decision 5: every non-completed result * (failed/stalled/cancelled) — and every spawn/wait exception — writes a * `delegate-failure.md` marker so the failure is never silent. The marker * includes status, error (secrets redacted), task identity, occurrence time, * recovery advice, and available session diagnostics. */ export interface DelegateFailureInfo { /** Final session status: "failed" | "stalled" | "cancelled" | "error" (exception path). */ status: string; /** Original error value (Error, message string, or unknown). */ error: unknown; /** Task identity for the marker header. */ task?: { id: string; title?: string; }; /** Session diagnostics available from the spawner handle (best-effort). */ session?: { id?: string; backend?: string; tmuxSessionName?: string; paneId?: string; createdAt?: string; }; /** ISO-8601 occurrence time (defaults to now). */ occurredAt?: string; /** Recovery advice; defaults to the generic re-dispatch guidance. */ recoveryAdvice?: string; } /** * Redact common secret shapes from free-text diagnostics before they are * persisted to evidence (task scope: never write secrets). * * Masks OpenAI-style `sk-…` keys, `Bearer …` tokens, and * `key=value`/`key: value` assignments for api keys/tokens/secrets/passwords. */ export declare function redactSecrets(text: string): string; /** * Write a delegate-failure marker into the task's evidence directory. * * Used for every non-completed session result (failed/stalled/cancelled) and * for spawn/wait exceptions (L1 transient / L4 fatal) so the failure is never * silent: evidence/delegate-failure.md explains why the task did not complete * and records status/error/task/time/recovery advice plus session diagnostics. * The error text is redacted via {@link redactSecrets} before persisting. */ export declare function writeDelegateFailure(evidenceDir: string, info: DelegateFailureInfo): Promise; /** * aiws change tasks execute * * Reads tasks.jsonl, validates the DAG, sorts tasks topologically, and for * each ready task writes handoff artifacts. When strategy is "l1" (or auto-detect * returns l1), this is dispatch-only: writes artifacts and marks as in_progress. * When strategy is "tmux", it uses SessionSpawner to supervise the full cycle: * spawn sub-agent session, wait for completion, and update status accordingly. * * Default strategy (no explicit --strategy): tmux is the unified dispatch path * (subagent-tmux-unified). detectSessionSpawner returns "tmux" when both tmux * and the CLI binary are available; otherwise it degrades to "l1" (dispatch-only) * WITHOUT throwing — a missing tmux/CLI never aborts the command. * @param options - Command options * @param options.changeId - The change ID * @param options.gitRoot - The git repository root * @param options.dryRun - If true, only print the execution plan without writing artifacts * @param options.strategy - Execution strategy: "tmux" or "l1". Omitted → 默认 tmux(tmux+CLI 均可用时),任一不可用时自动降级 l1(dispatch-only,不报错)。显式 --strategy tmux 属硬性要求:spawner 不可用时抛错(不静默降级)。 * @param options.timeoutMs - Max wait time in ms for supervise cycle (default: 1_800_000) * @param options.cliCommand - Explicit CLI command for spawned sessions. Highest priority: when set, the structured * Pi runtime profile (provider/model/thinking) is ignored and the command is used verbatim (never parsed/rewritten). * @param options.provider - Structured Pi `--provider` (only used when no explicit cliCommand is set). * @param options.model - Structured Pi `--model` (only used when no explicit cliCommand is set). * @param options.thinking - Structured Pi `--thinking` level (only used when no explicit cliCommand is set). */ export declare function runChangeTasksExecuteCommand(options: { changeId: string; gitRoot: string; dryRun?: boolean; strategy?: "tmux" | "l1"; timeoutMs?: number; cliCommand?: string; provider?: string; model?: string; thinking?: string; readyMode?: "tui" | "none"; /** undefined = new default split layout; false = explicit --no-split fallback. */ split?: boolean; splitLayout?: "h" | "v"; /** 续跑目标(pi-tmux-resume-continuation L2-b):tmux session 名、pi session 路径或部分 UUID。 */ resume?: string; /** D2 并行派发:对无依赖 ready 任务并发 spawn + waitAll(默认 1 = 串行)。 */ parallel?: number; /** M5 (ADR #16): 显式解除 batch admission freeze(resume-admission);未冻结时 no-op warn。 */ resumeAdmission?: boolean; }): Promise; export {};