import type { JobRecord, TaskStatus, TaskArgs, UpdateResult } from './types'; import { type ContextStrategy } from '../context/types'; /** 统计窗口默认大小:最近 N 条任务 */ export declare const DEFAULT_STATS_WINDOW = 50; /** 统计持久化文件(CLI stats 子命令读取同一路径) */ export declare const STATS_FILE: string; /** 按代理推断默认上下文策略(直接读 DEFAULT_CONTEXT_CONFIG.strategy,`?? 'none'` 兜底) */ export declare function defaultStrategyFor(agent: string): ContextStrategy; /** * 复用建议短文本(软预算三态): * - 已采样且未超过 maxInputTokens → 「最近输入 N」(预算不是硬限制,不宣称保证安全); * - 已采样且严格超过 → 「超预算,不建议复用」; * - 未采样(reuseInput 缺失/未知)→ 「输入未知」。 */ export declare function reuseHint(job: JobRecord, maxInputTokens: number): { text: string; over: boolean; }; /** 参与统计的指标记录(从 JobRecord 抽取) */ export interface StatsRecord { agent: string; strategy: string; status: TaskStatus; latencyMs?: number; tokens?: { input: number; output: number; }; } /** 按 (strategy, agent) 聚合的统计桶 */ export interface StatsBucket { strategy: string; agent: string; count: number; successCount: number; successRate: number; avgLatencyMs: number; latencySamples: number; avgTokens: number; tokenSamples: number; } /** * 滑动窗口聚合统计(纯函数): * - records 需按时间升序传入,仅取最近 window 条 * - 按 (strategy, agent) 分组,输出成功率 / 平均延迟 / 平均 token * - 缺少 latencyMs / tokens 的样本不影响对应均值,仅不计入样本数 */ export declare function computeStats(records: StatsRecord[], window?: number): StatsBucket[]; export declare class TaskTracker { private jobs; /** (parentSessionId, callID) → alias 精确索引:同 parent 下 callID 唯一,跨 parent 同名不串 */ private callIndex; private counters; /** 最近一次 tool hook 收到的 parentSessionId */ private _currentParentSessionId; /** 已 reconcile 的父 session ID,避免同一轮重复 reconcile */ private _reconciledForParent; get currentParentSessionId(): string; /** 生成 (parentSessionId, callID) 索引键(\u0000 分隔,防拼接歧义) */ private callKey; /** 生成代理别名,如 fixer → "fix-1", explorer → "exp-2" */ private alias; /** * 在 tool.execute.before 中调用,注册即将执行的任务。 * * 传入 callID 时建立 (parentSessionId, callID) → alias 的精确关联(推荐入口路径): * 同一 (parent, callID) 重复注册幂等——返回原 alias、不新建 job、不覆盖已有状态。 * 不传 callID 为 legacy 路径:仅注册无精确关联的 job(更新端不得用"最近匹配")。 */ registerBeforeTask(parentSessionId: string, args: TaskArgs, callID?: string): string; /** * 按 (parentSessionId, callID) 精确查找任务(不跨 parent、不猜最近)。 */ getJobByCall(parentSessionId: string, callID: string): JobRecord | undefined; /** * 按 (parentSessionId, callID) 精确更新任务(tool.execute.after / 明确终态入口)。 * 未知 callID:安全 no-op(reason='no-match'),绝不回退到"最近 running job"。 */ updateAfterTaskByCall(parentSessionId: string, callID: string, status: TaskStatus, sessionId?: string): UpdateResult; /** * 精确更新活动任务的 background 标记(前台 → 后台 promotion,Wave5 入口使用)。 * * 守卫: * - 按 (parentSessionId, callID) 精确定位;未知 → no-match(绝不回退最近 job); * - 仅 running 可更新;已终态(completed/errored/cancelled)→ idempotent 零副作用 * (不复活、不把历史任务当活动任务修改); * - 不触碰其他 parent 同 callID 的 job(键含 parentSessionId)。 */ setBackgroundByCall(parentSessionId: string, callID: string, bg: boolean): UpdateResult; /** * 按 alias 精确查找任务(关闭/收尾回调按 alias 定位 parent 等场景使用)。 * 未知 alias 返回 undefined;不改写任何状态。 */ getJobByAlias(alias: string): JobRecord | undefined; /** * 按 alias 精确更新任务(入口在已知 alias 时使用;未知 alias 安全 no-op)。 */ updateByAlias(alias: string, status: TaskStatus, sessionId?: string): UpdateResult; /** * 前台/后台统一的单任务更新语义: * - 前台任务:写入终态 + terminalReconciled + finalize;已终态再次更新为幂等(不覆盖原终态)。 * - 后台任务:仅绑定 childSessionId、保持 running(终态由入口依据真实 output 确认)。 */ private applyCallUpdate; /** * 在 tool.execute.after 中调用,更新任务完成状态(legacy 无 callID 路径)。 * * ⚠️ 仅允许无歧义的单一匹配:该 parent 下 running job 恰好 1 个时才更新; * 多个 running(并发)返回 reason='ambiguous' 并 no-op——绝不用"最近创建"猜测, * 防止并发乱序错配。精确更新请使用 updateAfterTaskByCall(parent, callID, ...)。 * * 非背景任务:立即标终态;背景任务:仅绑定 sessionId(等 event hook 处理)。 */ updateAfterTask(parentSessionId: string, status: TaskStatus, sessionId?: string): UpdateResult; /** * 根据子 session ID 更新任务状态(legacy 供 event hook 使用)。 * * 守卫: * - sessionId 关联多个 job(复用历史/并发)→ 归属不明,安全 no-op(reason='ambiguous'), * 绝不批量终结歧义活动任务,旧调用终态不覆盖新调用; * - 唯一关联但非 background running(前台或已终结)→ no-match,不操作。 * 精确语义请优先使用 updateAfterTaskByCall / updateByAlias。 */ updateByChildSessionId(sessionId: string, status: TaskStatus): UpdateResult; /** * 观测子会话 idle 事件(会话级 idle 不是成功证据:只记录观测,不终结任务)。 * * 入口(Wave4 接线)应结合 tool.execute.after 的真实 output 确认终态后, * 再调用 updateAfterTaskByCall / updateByAlias 落终态。 * 同 child session 关联多个调用(复用历史/并发)→ 归属不明,安全 no-op。 */ noteChildSessionIdle(sessionId: string): UpdateResult; /** * 按子会话 ID 查找唯一活动(running)任务,用于 idle 收尾前的精确定位(Wave4.1)。 * 已结束历史(completed/errored/cancelled)不参与计数——判断依据是"running 数", * 不是"关联总数",修复复用场景下旧历史导致新活动任务永远歧义、永不收尾的问题。 * * @returns ok=true 返回唯一 running job;ok=false 且 reason='no-match'(无 running) * 或 'ambiguous'(多个 running 共享该 childID,保守不选,绝不批量终结)。 */ findActiveByChildSessionId(sessionId: string): { ok: true; job: JobRecord; } | { ok: false; reason: 'no-match' | 'ambiguous'; }; /** * 明确终结指定活动任务(复用场景安全收尾专用,Wave4.1)。 * * 不复用 applyCallUpdate(后台分支只绑定不终结),直接从 running 落真实终态。 * 全部守卫命中才落终态,任何一项不满足即安全 no-op: * 1. 精确定位:按 (parentSessionId, callID) 精确关联;未知 → no-match(绝不回退最近)。 * 2. 状态:仅 running 可落真实终态;已终态 → idempotent(零副作用,stats 不重复写)。 * 3. 拒绝非终态写入:status 传 'running' → no-match(本 API 只用于收尾)。 * 4. 预期 childID:传入时必须与 job.sessionId 一致(job 未绑定时允许首次绑定); * 已绑定且不一致 → 绑定对象已变更(await 竞态/新调用复用),旧候选不得更新新调用 → no-match。 * 5. 唯一活动归属:预期 childID 下共享该 ID 的 running job 多于 1 → ambiguous no-op * (绝不批量终结多个 active;已结束历史不参与计数)。 * 6. 父会话隔离:callID 键含 parentSessionId,跨 parent 同名不串。 * 7. terminal stats 只写一次:仅在 running → terminal 转换时 finalize + persistStats。 */ finalizeByCall(parentSessionId: string, callID: string, expectedChildId: string | undefined, status: TaskStatus): UpdateResult; /** * 生成 Background Job Board 文本。 * * 必须显式传入父会话 ID,按 job.parentSessionId 精确过滤,不做任何全局回退: * 非 string(undefined / null / 数字等)或空串一律返回 ''。 * * Reusable Sessions 筛选(与 terminalReconciled 彻底分离——终态核对只用于 prune 回收, * 不决定复用可见性,修复真实 finalize 路径后正常完成 job 不再从 Board 消失): * - 同一 sessionId 只取最新注册调用(任意状态):最新必须是 background + completed 才展示, * errored/cancelled/running/foreground 最新一律遮住旧 completed——第二轮失败/取消后 * 不回退展示第一轮记录;同毫秒 createdAt 时后注册优先(Map 插入序,>= 覆盖); * - 正在被复用(该 sessionId 存在 running 绑定,含前台复用)的 session 不展示(活动中不可再复用); * - 未绑定 childID(sessionId 为空)无法复用不展示。 * * 复用建议(软复用预算,reuse 参数可选): * - 不传 reuse(策略关闭/未配置)→ 输出与旧版完全一致,不含建议列/提示; * - 传入 { maxInputTokens } 时对 Reusable Sessions 追加简短建议 * 「最近输入 N / 超预算,不建议复用 / 输入未知」三态; * 预算不是硬限制——低于阈值仅显示用量,不宣称「保证安全」; * 建议直接使用 quality.reuseInput 采样(随 idle 质量回送更新), * 绝不在每次 Board 请求时读取会话历史(无独立缓存系统)。 */ getBoardText(parentSessionId: string, reuse?: { maxInputTokens: number; }): string; /** * 标记任务已调和(不再显示在 Unreconciled 中) */ reconcileJob(alias: string): void; /** * 清理超时的背景任务(超过 timeoutMs 仍 running 的标为 errored)。 * * 额外收集并返回这些超时任务的 sessionId 列表,供调用方真正执行 session.abort()。 * * @returns 被标为 errored 的超时任务的 sessionId 列表(无 sessionId 的任务不包含) */ cleanupStaleJobs(timeoutMs: number): string[]; /** * 清理超龄终态任务:删除 terminalReconciled 且超过 olderThanMs 未更新的终态 job。 * 绝不会删除 running/pending 的活跃任务。 * * @param olderThanMs 超龄阈值(毫秒),超过此时间的终态任务会被删除 * @returns 实际删除的任务数量 */ pruneTerminalJobs(olderThanMs: number): number; /** * 检查任务是否可复用 */ isReusable(alias: string): boolean; /** 获取所有正在运行的 agent 类型列表 */ getRunningAgents(parentSessionId: string): string[]; /** 获取父会话下所有运行中的任务数量 */ getRunningCount(parentSessionId: string): number; /** 根据子 session ID 查找完整 JobRecord */ getJobBySessionId(sessionId: string): JobRecord | undefined; /** * 质量回送:把任务质量度量写回 JobRecord(负反馈闭环) * 由 event hook 在 session.idle 时调用 * * legacy 签名保留:按 sessionId 匹配,但**只允许安全单一匹配**—— * 同一 sessionId 关联多个 job(复用历史/并发)时归属不明,安全 no-op 不猜。 * 精确写回请使用 updateQualityByAlias(入口异步回调已持有捕获的 job.alias)。 */ updateQuality(sessionId: string, quality: NonNullable): void; /** * 按 alias 精确写回质量(capture 质量异步回调已持有 job.alias 时使用)。 * 未知 alias → no-match(不以 sessionId 首条匹配,绝不扩散到复用会话的新 alias)。 */ updateQualityByAlias(alias: string, quality: NonNullable): UpdateResult; /** * 终态收尾:写入终态即视为「已与父会话核对」。 * * `terminalReconciled` 仅保留 prune 语义:允许 `pruneTerminalJobs()` 回收。 * Board 的 Reusable Sessions 筛选已不依赖本标记(见 getBoardText:按 session 最新 * completed 且非活动中展示)——真实 finalize 路径(finalizeByCall/updateByChildSessionId) * 落终态后 job 仍保持复用可见性,不再从 Board 消失。 * * 背景任务的完成路径是 `session.idle` → `updateByChildSessionId()` → `finalize()`, * 该路径原先不设本标记,而唯一的补设点是 `registerBeforeTask()` 的 `_reconciledForParent` * 分支(要求同一 parent **再次**注册任务)。会话切换/结束后旧 parent 不再注册, * 旧背景任务便永不满足 prune 条件 → 永久留在 jobs Map,且每轮 `getBoardText()` 都被遍历。 * * 语义判断:finalize 只在这些终态写入路径上被调用(updateAfterTask 前台完成、 * updateByChildSessionId、cleanupStaleJobs、updateQuality、markCancelled、abortJob), * 其中 markCancelled/abortJob 已经显式设过标记——即「进入终态」在本模块内本就 * 等价于「已核对」,因此在 finalize 内统一补设语义是正确的,不是权宜之计。 */ private finalize; /** 收集全部终态任务为统计记录(按创建时间升序) */ private collectStatsRecords; /** 当前统计(内存态,按 (strategy, agent) 聚合;window 为滑动窗口条数) */ getStats(window?: number): StatsBucket[]; /** 把最近 window 条统计记录持久化到 STATS_FILE,供 CLI stats 子命令读取 */ persistStats(window?: number): void; /** cancel_task 集成:根据别名或 sessionId 标记任务为已取消并 reconcile */ markCancelled(taskId: string): void; /** * 只读解析取消目标(close_job 底层内部使用,不修改任何状态)。 * * 取消语义(与 abortJob 的 session 收紧一致): * - taskId 为 alias:直接命中该 job(**可能已终态**——调用方必须检查 job.status==='running' * 才执行真实 session.abort,旧 alias 命中已终态绝不得中止真实复用 session); * - taskId 为 sessionId:只认**唯一活动 running**(已结束历史不抢当前); * 多个 running 共享该 child → ambiguous(保守拒绝,绝不批量取消); * 无 running → no-match(不返回历史,避免调用方 abort 复用中的真实 session)。 * * @returns ok=true 返回目标 job;ok=false 且 reason='no-match' | 'ambiguous' */ resolveCancelTarget(taskId: string): { ok: true; job: JobRecord; } | { ok: false; reason: 'no-match' | 'ambiguous'; }; /** * 按 alias 将仍 running 的任务落 cancelled(close_job 在真实 SDK abort 成功后调用)。 * 未知 alias → no-match;已终态(竞态:abort 期间被其他路径终结)→ idempotent 零副作用, * 绝不把 completed/errored 改回 cancelled(不复活/变更历史)。 */ markCancelledByAlias(alias: string): UpdateResult; /** * 关闭(中止)一个卡住/不必要的子代理后台任务。 * * 按 taskId 定位任务(安全兼容契约): * - alias 直接命中 this.jobs(保持既有幂等守卫:已终态返回原状态不改写); * - sessionId 路径: * · 恰好 1 个 running → 命中该 active(已结束历史不抢当前); * · 多个 running 共享该 child → undefined(歧义拒绝,绝不批量取消); * · 无 running 且恰好 1 条匹配历史 → **幂等返回原状态**(旧调用方兼容—— * 注意这不等于允许中止真实复用 session:job-control 走 resolveCancelTarget * 只认活动 running,不会据此对历史发 SDK abort); * · 无 running 且多条匹配历史 → undefined(歧义 no-op,绝不任取一条); * · 无任何匹配 → undefined。 * 只做状态同步(running → cancelled 并 finalize/persist;已终态零副作用), * 返回真正的 sessionId 供调用方执行 session.abort()——本方法保持无 IO 依赖,便于单测。 * * @param taskId 子 session ID(ses_xxx)或任务 alias(如 coe-1) * @returns 找到任务时返回 { sessionId, job };歧义/未找到返回 undefined */ abortJob(taskId: string): { sessionId?: string; job: JobRecord; } | undefined; /** abortJob 共用落状态逻辑(保持既有幂等语义) */ private abortFound; } //# sourceMappingURL=tracker.d.ts.map