@tea-agent/loop-agent 0.29.1 → 0.29.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +36 -16
  2. package/bin/agent-worker.js +0 -0
  3. package/dist/commands/client-recovery.js +2 -10
  4. package/dist/commands/init.js +1 -1
  5. package/dist/executors/dag-pi-executor.js +35 -10
  6. package/dist/executors/pi-executor.js +5 -3
  7. package/dist/executors/pi-playwright-cli-tool.js +9 -14
  8. package/dist/executors/pi-sdk-executor.js +16 -0
  9. package/dist/shared/operator/capabilities.js +9 -9
  10. package/dist/shared/pi-retry-settings.js +23 -0
  11. package/dist/worker/console/chat/pi-runtime.js +167 -41
  12. package/dist/worker/console/chat/routes.js +21 -2
  13. package/dist/worker/console/chat/runtime-context.js +50 -8
  14. package/dist/worker/console/chat/tool-preview.js +90 -0
  15. package/dist/worker/console/chat/turn-process.js +178 -0
  16. package/dist/worker/console/chat/usage.js +144 -16
  17. package/dist/worker/console/night-aux-ticker.js +141 -0
  18. package/dist/worker/console/operation-runner.js +21 -4
  19. package/dist/worker/console/operator-user-error.js +12 -0
  20. package/dist/worker/console/recovery-cta.js +6 -6
  21. package/dist/worker/console/routes.js +61 -0
  22. package/dist/worker/console/server.js +7 -0
  23. package/dist/worker/console/static/assets/index-D9gnJn_l.js +29 -0
  24. package/dist/worker/console/static/assets/index-rajoXwkM.css +1 -0
  25. package/dist/worker/console/static/index.html +2 -2
  26. package/dist/worker/console/static-src/night-prepare-result.js +118 -0
  27. package/dist/worker/scheduler/clock-install/win32-schtasks.js +70 -15
  28. package/dist/workflows/dag/backend-test-writer-completeness.js +98 -14
  29. package/dist/workflows/dag/frontend-test-case-checklist.js +3 -3
  30. package/dist/workflows/dag/init-hybrid.js +21 -12
  31. package/dist/workflows/dag/node-execution.js +41 -2
  32. package/docs/templates/frontend-test-case-checklist.md +1 -1
  33. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +1 -1
  34. package/docs/templates/frontend-test-dag.json +1 -1
  35. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  36. package/docs/templates/frontend-test-dag.review-cases.prompt.md +1 -1
  37. package/docs/templates/init-managed-agents.md +6 -3
  38. package/package.json +1 -1
  39. package/skills/playwright-cli/SKILL.md +1 -1
  40. package/skills/playwright-cli-case-generator/SKILL.md +1 -1
  41. package/dist/worker/console/static/assets/index-Cwx-ZVEQ.js +0 -29
  42. package/dist/worker/console/static/assets/index-Yyn3ynVv.css +0 -1
package/CHANGELOG.md CHANGED
@@ -2,34 +2,54 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ## [0.29.1] - 2026-08-07
5
+ ## [0.29.3] - 2026-08-07
6
6
 
7
7
  ### 重点更新
8
8
 
9
- - 前端测试默认执行流程大幅精简,收敛至约 11 个静态节点,合并了多项检查与报告步骤
10
- - 前端测试浏览器默认改为无头(headless)模式,减少环境干扰
11
- - 优化证据缺失处理逻辑,非路径逃逸的异常不再强制阻断报告生成
12
- - 修复文档说明被误判为可执行命令的问题,提升执行稳定性
9
+ - 夜间任务控制台升级为引导式工作台,支持点选预约、审阅写入集与任务看板,并保留人工审批门禁
10
+ - 失败 DAG 默认改为从失败节点安全续跑,避免因服务波动无理由新建任务
11
+ - Operator Chat 支持内联展示工具执行过程,并改进 Pi 用量统计的准确解析与状态栏展示
12
+ - 增强 Pi 网关限流容错,遇到 429 状态码时自动按 60 秒间隔退避重试
13
13
 
14
14
  ### 新增
15
15
 
16
- - 新增前端测试标准场景物化节点,支持执行后针对阻塞或缺失证据进行最多两轮有界重跑
17
- - 新增前端测试标准场景规范模板(frontend-test-standard-scenarios.v1.json)
18
- - 前端测试独占节点支持 tools-only 写保护策略,在保留工具级写沙箱的同时跳过 Git 基线的硬失败限制
16
+ - 控制台新增前台辅助夜间任务调度开关,在控制台开启时可按间隔触发调度(仅前台有效,过夜仍需系统定时器)
19
17
 
20
18
  ### 改进
21
19
 
22
- - 前端测试默认 DAG 结构精简至约 11 个静态节点:合并双重重跑、检查清单与清单文件、证据与结果、L5 与 HTML 报告
23
- - 前端测试主 HTML 报告不再依赖回顾(retrospect)节点,回顾功能改为可选开启
24
- - 权威清单文件(manifest.json)仅记录规范化后的测试用例,并在未配置重试时省略相关静态节点
25
- - 强化运营 HTML 报告的状态过滤功能,并优化失败与阻塞原因的展示
20
+ - 失败 DAG 恢复策略改为先诊断再从失败节点续跑,统一对齐系统提示、恢复入口与工具说明
21
+ - Operator Chat 将工具执行过程内联至消息时间线,支持折叠查看,切会话后可恢复工具记录
22
+ - Operator Chat 用量栏改进 Pi 用量解析,准确展示输入、输出、缓存读取与费用信息
23
+ - 注入上下文面板升级为更大可读面板,展示脱敏后的完整系统提示、技能正文与激活工具列表
26
24
 
27
25
  ### 修复
28
26
 
29
- - 修复前端测试检查清单将不可用或阻塞状态的说明文字误判为非法可执行命令的问题
30
- - 修复前端测试收尾阶段因证据缺失或格式错误导致强制失败的问题,现仅对路径逃逸强制报错,其余降级为建议性提示
31
- - 修复前端测试合并后清单文件物化异常的问题,确保仅写入规范化用例并恢复负向测试用例
32
- - 回退误改的全局默认 Pi 模型(DEFAULT_PI_MODEL),将其恢复为 glm-5.2,模型路由统一交由 harness 复杂度矩阵处理
27
+ - 修复 Windows 计划任务因 /TR 参数超过 261 字符限制导致创建失败的问题,改用短 .cmd 路径绕过
28
+ - 修复 package-lock 中硬编码的镜像源地址导致发布链路 npm ci 失败的问题,统一指向官方源
29
+ - 修复 playwright-cli open 传入不支持的 --headless/--headed 参数报错的问题
30
+ - 修复 Pi 网关返回 429 限流时重试过快导致任务失败的问题,现自动识别并按 60 秒间隔退避
31
+ - 修复标准 DAG 节点将任务相对路径误当作仓库根路径读取,导致找文件超时的问题
32
+ - 修复 Operator Chat 重开会话时因 JSONL 丢失而静默新建会话 ID 导致会话不匹配的问题
33
+ - 修复夜间任务异步执行结果丢失及 agent-worker 可执行文件权限的问题
34
+
35
+ ## [0.29.2] - 2026-08-07
36
+
37
+ ### 重点更新
38
+
39
+ - 优化后端测试生成 writer 的续写重试机制,直接内嵌具体目标路径以恢复输出,不再依赖读取受限路径
40
+ - 修复三处完整性门禁(Completeness Gate)误报,避免将正确的测试生成误判为残缺写集
41
+ - 修复标准 Happy Path / `contract-pi` 等节点 source prompt:只暴露仓库相对可读路径(`.harness/tasks/<taskId>/source/...`),并明确禁止把 task-relative 的 `source/需求.md` 当工具读路径
42
+
43
+ ### 改进
44
+
45
+ - 后端测试生成 writer(N2 MD / N5 pytest)在输出截断或残缺写集后,续写重试直接内嵌具体目标路径清单,不再依赖读取 `.harness/**` 路径
46
+ - 即使遇到结果为空或输出失败的异常,也会评估完整性门禁,将可恢复的部分写入升格为不完整写集,避免重试空转
47
+
48
+ ### 修复
49
+
50
+ - 修复 README 正文提及的编号式文件名被误判为缺失模块的问题
51
+ - 修复覆盖率矩阵(Coverage Matrix)表头识别被覆盖率范围(Coverage Scope)数据行遮蔽的问题
52
+ - 修复合法多行 `def` 的 pytest 脚本被误判为截断的问题(真正截断现仍由括号配平计数准确捕获)
33
53
 
34
54
  ## [0.29.0] - 2026-08-07
35
55
 
File without changes
@@ -18,16 +18,8 @@ export const BACKOFF_MS = [2000, 4000, 8000, 16000, 30000];
18
18
  export const MAX_SESSION_RETRIES = 5;
19
19
  /** Hard cap for OpenCode overflow compact+resume recoveries (≤ 2). */
20
20
  export const MAX_OVERFLOW_RECOVERIES = 1;
21
- export const PI_RECOMMENDED_RETRY = {
22
- enabled: true,
23
- maxRetries: 5,
24
- baseDelayMs: 3000,
25
- provider: {
26
- maxRetries: 0,
27
- maxRetryDelayMs: 60000,
28
- },
29
- };
30
- export const PI_RECOMMENDED_COMPACTION = { enabled: true };
21
+ import { PI_RECOMMENDED_COMPACTION, PI_RECOMMENDED_RETRY, } from "../shared/pi-retry-settings.js";
22
+ export { PI_RECOMMENDED_COMPACTION, PI_RECOMMENDED_RETRY };
31
23
  /**
32
24
  * Shared overflow phrase sources used by isContextOverflow and generated plugins.
33
25
  * Keep English + Chinese intranet gateway messages in one place to avoid drift.
@@ -2217,7 +2217,7 @@ function buildTargetFeatureWorkflow(input) {
2217
2217
  '- `taskKind: "backend-test"` selects the dedicated backend test DAG. Its Pi nodes analyze requirements, generate and review backend cases, generate pytest, and retrospect on results; shell gate/execution nodes enforce the review verdict and run the target project\'s pytest. The backend test templates (`backend-test-dag.json` and the `backend-test-dag.*.prompt.md` files) ship inside the loop-agent package as static references and are projected to target projects under the governance `templates/` directory.',
2218
2218
  '- `taskKind: "knowledge-sync"` selects the Feature-scoped test-knowledge write-back DAG (collect → draft → validate → apply → pointer). Bind `featureId` in `task.json` (or hardConstraints / requirement text). It writes only under `features/<featureId>/…` after final verification evidence exists.',
2219
2219
  '- `taskKind: "knowledge-graph-bootstrap"` selects the business knowledge-graph bootstrap DAG (preflight → inventory → propose → validate → review → gate → promote → materialize). AI writes only `knowledge/bootstrap/staging/**`; promote is merge-new-only.',
2220
- '- `taskKind: "frontend-test"` selects the FE-test RAG DAG. It writes a traceable frontend RAG package and Markdown case manifest, then executes manifest cases serially with `playwright-cli` in isolated test environments and retains per-case evidence. It never generates pytest or Playwright source code. `frontendTest.maxCasesPerBatch` defaults to 20 (maximum 50); optional `maxTokensPerCase` and `maxTotalTokens` stop only later cases after a completed case\'s token usage is recorded, marking them `blocked: token-budget-exhausted`. Generated browser startup uses `playwright-cli open --browser=chrome --headless <base-url>`; the generic playwright-cli skill is unchanged.',
2220
+ '- `taskKind: "frontend-test"` selects the FE-test RAG DAG. It writes a traceable frontend RAG package and Markdown case manifest, then executes manifest cases serially with `playwright-cli` in isolated test environments and retains per-case evidence. It never generates pytest or Playwright source code. `frontendTest.maxCasesPerBatch` defaults to 20 (maximum 50); optional `maxTokensPerCase` and `maxTotalTokens` stop only later cases after a completed case\'s token usage is recorded, marking them `blocked: token-budget-exhausted`. Generated browser startup uses `playwright-cli open --browser=chrome <base-url>`; the generic playwright-cli skill is unchanged.',
2221
2221
  "- Only eligible read-only Pi nodes (planner, scout, reviewer, verifier, closeout with no write-capable tool profile) receive the conservative automatic retry policy. Supervisor, implementer, writer, docs-only, dynamic, shell, static, and decision-gate nodes are not retried automatically. Eligible nodes cannot write repository files; the controller only records immutable attempt evidence under `.harness/dag-runs/<state>/<run-id>/<node-id>/attempt-<n>.json`.",
2222
2222
  "",
2223
2223
  "Use the package-backed public knowledge CLI for graph operations. Do not require target projects to run package-only kb runtime scripts:",
@@ -685,9 +685,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
685
685
  });
686
686
  }
687
687
  let completenessFailure;
688
- if (mapped.ok &&
689
- writeGuardOk &&
690
- !writerOutcomeViolation &&
688
+ if (writeGuardOk &&
691
689
  isBackendTestCompletenessRetryCandidate(input.task)) {
692
690
  try {
693
691
  const progress = input.task.id === "generate-backend-pytest-pi"
@@ -701,12 +699,25 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
701
699
  });
702
700
  if (progress.status !== "PASS") {
703
701
  const classified = classifyBackendTestWriterCompletenessFailure(progress);
704
- completenessFailure = {
705
- failureCategory: classified.recoverable
706
- ? INCOMPLETE_WRITE_SET_RETRY_CATEGORY
707
- : "invalid-output",
708
- detail: `backend-test completeness gate ${progress.status}: targets=${progress.targetPaths.join(",") || "(none)"}; issues=${progress.issues.map((issue) => issue.detail).join("; ") || "none"}`,
709
- };
702
+ // A recoverable partial write set (missing/broken files) always wins
703
+ // over empty-output / invalid-output / writer-empty-diff: it carries
704
+ // the concrete target paths the continuation attempt needs.
705
+ if (classified.recoverable) {
706
+ completenessFailure = {
707
+ failureCategory: INCOMPLETE_WRITE_SET_RETRY_CATEGORY,
708
+ detail: `backend-test completeness gate ${progress.status}: targets=${progress.targetPaths.join(",") || "(none)"}; issues=${progress.issues.map((issue) => issue.detail).join("; ") || "none"}`,
709
+ };
710
+ }
711
+ else if (mapped.ok && !writerOutcomeViolation) {
712
+ // Non-recoverable completeness issue only overrides a clean
713
+ // successful path; on an already-failed attempt, keep the
714
+ // executor's original category so retries reflect the real
715
+ // cause (empty-output / invalid-output / writer-empty-diff).
716
+ completenessFailure = {
717
+ failureCategory: "invalid-output",
718
+ detail: `backend-test completeness gate ${progress.status}: targets=${progress.targetPaths.join(",") || "(none)"}; issues=${progress.issues.map((issue) => issue.detail).join("; ") || "none"}`,
719
+ };
720
+ }
710
721
  }
711
722
  }
712
723
  catch (error) {
@@ -746,7 +757,21 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
746
757
  ? WRITER_EMPTY_DIFF_RETRY_CATEGORY
747
758
  : "invalid-output"
748
759
  : "write-guard"
749
- : mapped.failureCategory,
760
+ : // When the attempt already failed with a writer-style category
761
+ // (empty-output / invalid-output / writer-empty-diff) but the workspace
762
+ // shows a recoverable partial write set, prefer incomplete-write-set so
763
+ // the continuation is retryable and carries the embedded target paths.
764
+ // Provider/transport failures (quota, auth, network, timeout,
765
+ // rate-limit, unavailable) keep their original category so a provider
766
+ // outage is never masked as a recoverable local write problem.
767
+ writeGuardOk &&
768
+ completenessFailure?.failureCategory ===
769
+ INCOMPLETE_WRITE_SET_RETRY_CATEGORY &&
770
+ (mapped.failureCategory === "empty-output" ||
771
+ mapped.failureCategory === "invalid-output" ||
772
+ mapped.failureCategory === WRITER_EMPTY_DIFF_RETRY_CATEGORY)
773
+ ? INCOMPLETE_WRITE_SET_RETRY_CATEGORY
774
+ : mapped.failureCategory,
750
775
  durationMs: mapped.durationMs || Date.now() - started,
751
776
  };
752
777
  }
@@ -961,10 +961,12 @@ export function classifyPiFailure(input) {
961
961
  if (input.exitCode === 0 && input.assistantText.trim())
962
962
  return "success";
963
963
  const combined = `${input.stderr}\n${input.stdout}`.toLowerCase();
964
- if (/quota|usage limit|reached.*limit|5\s*小时|5小时/.test(combined))
965
- return "quota";
966
- if (/rate.?limit/.test(combined))
964
+ // Prefer explicit rate-limit / HTTP 429 before generic "limit" quota phrases.
965
+ // Chinese gateways often say "请求频率已达到限制" without English "rate limit".
966
+ if (/rate.?limit|too many requests|\b429\b|请求频率|频率.*限制|rpm|tpm/.test(combined))
967
967
  return "rate-limit";
968
+ if (/quota|usage limit|reached.*limit|5\s*小时|5小时|insufficient_quota|billing/.test(combined))
969
+ return "quota";
968
970
  if (/\bunauthorized\b|\bhttp\s*40[13]\b|invalid api key|authentication (?:failed|required|error)|auth(?:entication)? failed/.test(combined))
969
971
  return "auth";
970
972
  if (/unknown provider|unknown model|model.*unavailable|provider.*unavailable|\bunavailable\b|overloaded|capacity|temporarily unavailable/.test(combined))
@@ -197,21 +197,19 @@ function validateOpenArgs(args, baseUrl) {
197
197
  for (const arg of args) {
198
198
  assertNoControlMeta(arg);
199
199
  assertNoSessionFlag(arg);
200
- if (arg === "--browser=chrome" || arg === "--headless") {
200
+ if (arg === "--browser=chrome") {
201
201
  flags.add(arg);
202
202
  normalized.push(arg);
203
203
  continue;
204
204
  }
205
- // Accept legacy --headed but normalize to headless for frontend-test CI/dogfood.
206
- if (arg === "--headed") {
207
- flags.add("--headless");
208
- if (!normalized.includes("--headless"))
209
- normalized.push("--headless");
205
+ // playwright-cli defaults to headless and does not accept --headless/--headed.
206
+ // Strip legacy flags so model/docs remnants never reach the binary.
207
+ if (arg === "--headless" || arg === "--headed") {
210
208
  continue;
211
209
  }
212
210
  if (arg.startsWith("--browser=") ||
213
211
  arg === "--browser") {
214
- throw new PlaywrightCliPolicyError("open-browser-flags", "open must use --browser=chrome --headless only");
212
+ throw new PlaywrightCliPolicyError("open-browser-flags", "open must use --browser=chrome only (default headless; do not pass --headless/--headed)");
215
213
  }
216
214
  if (/^[a-z][a-z0-9+.-]*:/i.test(arg) || arg.startsWith("http")) {
217
215
  url = arg;
@@ -219,12 +217,9 @@ function validateOpenArgs(args, baseUrl) {
219
217
  }
220
218
  throw new PlaywrightCliPolicyError("open-args", `unsupported open argument: ${arg}`);
221
219
  }
222
- if (!flags.has("--browser=chrome") || !flags.has("--headless")) {
223
- // controller injects required flags when missing from model args
224
- if (!flags.has("--browser=chrome"))
225
- normalized.unshift("--browser=chrome");
226
- if (!flags.has("--headless"))
227
- normalized.push("--headless");
220
+ if (!flags.has("--browser=chrome")) {
221
+ // controller injects required browser flag when missing from model args
222
+ normalized.unshift("--browser=chrome");
228
223
  }
229
224
  if (!url) {
230
225
  throw new PlaywrightCliPolicyError("open-url-required", "open requires an absolute http(s) URL");
@@ -515,7 +510,7 @@ export function preparePlaywrightCliArgv(input, ctx) {
515
510
  catch {
516
511
  throw new PlaywrightCliPolicyError("goto-url-invalid", "goto requires a URL relative to the controller baseUrl");
517
512
  }
518
- const openLike = validateOpenArgs(["--browser=chrome", "--headless", target], ctx.baseUrl);
513
+ const openLike = validateOpenArgs(["--browser=chrome", target], ctx.baseUrl);
519
514
  return openLike[openLike.length - 1];
520
515
  });
521
516
  if (args.length !== 1) {
@@ -2,6 +2,7 @@ import { appendFile, mkdir } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { BoundedTextPreview, classifyPiFailure, createPiJsonlStreamCollector, DEFAULT_ABORT_GRACE_MS, DEFAULT_STALL_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, extractAssistantTextFromPiJson, } from "./pi-executor.js";
4
4
  import { serializeSessionEvent } from "./pi-event-serializer.js";
5
+ import { PI_RECOMMENDED_COMPACTION, PI_RECOMMENDED_RETRY, } from "../shared/pi-retry-settings.js";
5
6
  let sdkSessionFactoryOverride;
6
7
  let sdkImportOverrideForTests;
7
8
  let sdkModuleOverrideForTests;
@@ -154,6 +155,7 @@ async function createSdkSession(sdk, input, shared) {
154
155
  const createAgentSession = sdk.createAgentSession;
155
156
  const SessionManager = sdk.SessionManager;
156
157
  const DefaultResourceLoader = sdk.DefaultResourceLoader;
158
+ const SettingsManager = sdk.SettingsManager;
157
159
  const getAgentDir = sdk.getAgentDir;
158
160
  const ModelRuntime = sdk.ModelRuntime;
159
161
  const agentDir = shared?.agentDir ?? getAgentDir();
@@ -168,12 +170,25 @@ async function createSdkSession(sdk, input, shared) {
168
170
  const model = input.provider && input.model
169
171
  ? await resolveModel(modelRuntime, input.provider, input.model)
170
172
  : undefined;
173
+ // Inject rate-limit-tolerant auto-retry settings for the session lifecycle.
174
+ // Prefer SettingsManager.inMemory so harness runs do not depend on ~/.pi or
175
+ // project .pi/settings.json being present/trusted.
176
+ const settingsManager = typeof SettingsManager?.inMemory === "function"
177
+ ? SettingsManager.inMemory({
178
+ retry: {
179
+ ...PI_RECOMMENDED_RETRY,
180
+ provider: { ...PI_RECOMMENDED_RETRY.provider },
181
+ },
182
+ compaction: { ...PI_RECOMMENDED_COMPACTION },
183
+ }, { projectTrusted: true })
184
+ : undefined;
171
185
  const loader = new DefaultResourceLoader({
172
186
  cwd: input.cwd,
173
187
  agentDir,
174
188
  noContextFiles: true,
175
189
  noSkills: true,
176
190
  noExtensions: true,
191
+ ...(settingsManager ? { settingsManager } : {}),
177
192
  appendSystemPromptOverride: (base) => [
178
193
  ...base,
179
194
  input.appendSystemPrompt,
@@ -191,6 +206,7 @@ async function createSdkSession(sdk, input, shared) {
191
206
  resourceLoader: loader,
192
207
  tools: input.toolNames,
193
208
  modelRuntime,
209
+ ...(settingsManager ? { settingsManager } : {}),
194
210
  ...(model ? { model } : {}),
195
211
  ...(input.thinking ? { thinkingLevel: input.thinking } : {}),
196
212
  ...(Array.isArray(input.customTools) && input.customTools.length > 0
@@ -776,7 +776,7 @@ export function buildOperatorCapabilitiesDocument() {
776
776
  resultSchemaVersion: 1,
777
777
  envelopeSchemaVersion: 1,
778
778
  requiredErrorCodes: ["NOT_FOUND", "INVALID_INPUT"],
779
- description: "DAG run handoff report by run id.",
779
+ description: "DAG run handoff report by run id. On failure, read primaryFailure.nodeId + primaryRecovery first; prefer dagRerunPlan from that node before standaloneTaskRerun or a new task.",
780
780
  inputParams: [
781
781
  {
782
782
  name: "runId",
@@ -919,19 +919,19 @@ export function buildOperatorCapabilitiesDocument() {
919
919
  resultSchemaVersion: 1,
920
920
  envelopeSchemaVersion: 1,
921
921
  requiredErrorCodes: ["INVALID_INPUT", "NOT_FOUND"],
922
- description: "Read-only subgraph rerun plan for confirmation (no mutation).",
922
+ description: "Read-only R1 subgraph plan (no mutation). Call before dagRerun. Prefer primaryFailure.nodeId from dagReport. If eligible=false (writer/decision/fingerprint), do not force execute — fall back to standaloneTaskRerun or same-task advance. Returns planHash required by execute.",
923
923
  inputParams: [
924
924
  {
925
925
  name: "runId",
926
926
  type: "string",
927
927
  required: true,
928
- description: "dag run id",
928
+ description: "completed failed|partial_failed parent dag run id",
929
929
  },
930
930
  {
931
931
  name: "fromNode",
932
932
  type: "string",
933
933
  required: true,
934
- description: "node id to rerun from",
934
+ description: "node id to rerun from (prefer failed/attention node from dagReport primaryFailure.nodeId)",
935
935
  },
936
936
  ],
937
937
  modelCallable: "always",
@@ -949,31 +949,31 @@ export function buildOperatorCapabilitiesDocument() {
949
949
  "BINDING_DRIFT",
950
950
  "INVALID_INPUT",
951
951
  ],
952
- description: "Execute continuation run from effective node with confirmed plan hash.",
952
+ description: "Execute R1 continuation from effective node. Requires planHash from a fresh dagRerunPlan (eligible=true). Prefer this over new task / standaloneTaskRerun for provider flake and safe downstream failures. Human Gate required.",
953
953
  inputParams: [
954
954
  {
955
955
  name: "runId",
956
956
  type: "string",
957
957
  required: true,
958
- description: "dag run id",
958
+ description: "parent dag run id (same as dagRerunPlan)",
959
959
  },
960
960
  {
961
961
  name: "fromNode",
962
962
  type: "string",
963
963
  required: true,
964
- description: "node id to rerun from",
964
+ description: "node id matching the planned dagRerunPlan",
965
965
  },
966
966
  {
967
967
  name: "planHash",
968
968
  type: "string",
969
969
  required: true,
970
- description: "rerun plan hash",
970
+ description: "sha256 plan hash from dagRerunPlan (must match exactly)",
971
971
  },
972
972
  {
973
973
  name: "reason",
974
974
  type: "string",
975
975
  required: true,
976
- description: "rerun reason",
976
+ description: "why continue from this node (e.g. provider flake; resume from review-pi)",
977
977
  },
978
978
  ],
979
979
  modelCallable: "prepare-only",
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Recommended Pi auto-retry settings for gateway rate limits.
3
+ *
4
+ * Work-hours gateways often enforce rolling windows (e.g. 100 req / 10 min).
5
+ * Pi's built-in default (2s / 4s / 8s) exhausts before the window recovers;
6
+ * a 60s base delay yields 1m → 2m → 4m … and tolerates transient 429s.
7
+ *
8
+ * Used by:
9
+ * - project / user `.pi` settings merge (`client-recovery`)
10
+ * - loop-agent Pi SDK executor (injected SettingsManager, independent of ~/.pi)
11
+ */
12
+ export const PI_RECOMMENDED_RETRY = {
13
+ enabled: true,
14
+ maxRetries: 5,
15
+ /** First retry wait; subsequent attempts use exponential backoff. */
16
+ baseDelayMs: 60_000,
17
+ provider: {
18
+ /** Disable nested SDK provider retries; outer auto-retry owns the budget. */
19
+ maxRetries: 0,
20
+ maxRetryDelayMs: 60_000,
21
+ },
22
+ };
23
+ export const PI_RECOMMENDED_COMPACTION = { enabled: true };