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 {};