@tea-agent/loop-agent 0.26.3 → 0.26.4

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.
@@ -9,17 +9,31 @@ export const repairFailureClassSchema = z.enum([
9
9
  "governance",
10
10
  "unknown",
11
11
  ]);
12
+ export const repairDispositionSchema = z.enum([
13
+ "repairable",
14
+ "blocked-boundary",
15
+ "no-op-pass",
16
+ ]);
12
17
  export const repairArtifactSchema = z.object({
13
18
  schemaVersion: z.literal(1),
14
19
  verdict: z.enum(["pass", "request-revision"]),
15
20
  failureClass: repairFailureClassSchema,
16
- rootCause: z
17
- .string()
18
- .min(1, { message: 'repair artifact field "rootCause" must be a non-empty string' }),
21
+ rootCause: z.string().min(1, {
22
+ message: 'repair artifact field "rootCause" must be a non-empty string',
23
+ }),
19
24
  fixScope: z.array(z.string().min(1)).default([]),
20
25
  invariant: z.string().min(1),
21
26
  evidenceRefs: z.array(z.string().min(1)).default([]),
22
27
  rawLogFallbackAllowed: z.boolean().default(false),
28
+ /**
29
+ * Optional disposition for convergence routing. Omitted values are treated as
30
+ * `repairable` for backward compatibility with older artifacts.
31
+ */
32
+ disposition: repairDispositionSchema.optional(),
33
+ /**
34
+ * Report-only out-of-boundary paths. Never expands write authority.
35
+ */
36
+ requiredScope: z.array(z.string().min(1)).optional(),
23
37
  });
24
38
  /**
25
39
  * Canonical rootCause substituted when a `verdict: "pass"` repair artifact
@@ -117,7 +131,23 @@ function scopeWithinPatterns(scope, patterns) {
117
131
  }
118
132
  export function validateRepairArtifactScope(input) {
119
133
  const { artifact, repairTask } = input;
134
+ const disposition = artifact.disposition ?? "repairable";
135
+ const requiredScope = artifact.requiredScope ?? [];
136
+ for (const scope of requiredScope) {
137
+ if (!isPathLikeScope(scope)) {
138
+ return {
139
+ ok: false,
140
+ reason: `requiredScope "${scope}" must be a concrete path or glob (report-only; does not expand write authority)`,
141
+ };
142
+ }
143
+ }
120
144
  if (artifact.verdict === "pass") {
145
+ if (disposition === "blocked-boundary") {
146
+ return {
147
+ ok: false,
148
+ reason: "pass repair artifact cannot use blocked-boundary disposition",
149
+ };
150
+ }
121
151
  if (artifact.fixScope.length > 0) {
122
152
  return {
123
153
  ok: false,
@@ -126,6 +156,67 @@ export function validateRepairArtifactScope(input) {
126
156
  }
127
157
  return { ok: true, artifact };
128
158
  }
159
+ if (disposition === "no-op-pass") {
160
+ return {
161
+ ok: false,
162
+ reason: "request-revision repair artifact cannot use no-op-pass disposition",
163
+ };
164
+ }
165
+ // blocked-boundary may report empty fixScope (nothing in-boundary to fix)
166
+ // while requiredScope describes the out-of-boundary need.
167
+ if (disposition === "blocked-boundary") {
168
+ if (requiredScope.length === 0) {
169
+ return {
170
+ ok: false,
171
+ reason: "blocked-boundary request-revision requires non-empty requiredScope (report-only)",
172
+ };
173
+ }
174
+ if (!repairTask) {
175
+ return {
176
+ ok: false,
177
+ reason: "repair artifact gate cannot find downstream repair task",
178
+ };
179
+ }
180
+ const allowed = repairTask.allowedPaths ?? [];
181
+ const writeSet = repairTask.writeSet ?? [];
182
+ const forbidden = repairTask.forbiddenPaths ?? [];
183
+ const hasOutOfBoundaryRequiredScope = requiredScope.some((scope) => {
184
+ const normalized = normalizePath(scope);
185
+ return (!scopeWithinPatterns(scope, allowed) ||
186
+ !scopeWithinPatterns(scope, writeSet) ||
187
+ forbidden.some((pattern) => pathMatchesPattern(normalized, pattern)));
188
+ });
189
+ if (!hasOutOfBoundaryRequiredScope) {
190
+ return {
191
+ ok: false,
192
+ reason: "blocked-boundary requiredScope must include a path outside repair task allowedPaths/writeSet",
193
+ };
194
+ }
195
+ if (artifact.fixScope.length > 0) {
196
+ for (const scope of artifact.fixScope) {
197
+ if (!isPathLikeScope(scope)) {
198
+ return {
199
+ ok: false,
200
+ reason: `fixScope "${scope}" must be a concrete path or glob inside repair task allowedPaths/writeSet`,
201
+ };
202
+ }
203
+ if (!scopeWithinPatterns(scope, allowed) ||
204
+ !scopeWithinPatterns(scope, writeSet)) {
205
+ return {
206
+ ok: false,
207
+ reason: `fixScope "${scope}" is outside repair task allowedPaths/writeSet`,
208
+ };
209
+ }
210
+ if (forbidden.some((pattern) => pathMatchesPattern(normalizePath(scope), pattern))) {
211
+ return {
212
+ ok: false,
213
+ reason: `fixScope "${scope}" matches repair task forbiddenPaths`,
214
+ };
215
+ }
216
+ }
217
+ }
218
+ return { ok: true, artifact };
219
+ }
129
220
  if (artifact.fixScope.length === 0) {
130
221
  return {
131
222
  ok: false,
@@ -148,7 +239,8 @@ export function validateRepairArtifactScope(input) {
148
239
  reason: `fixScope "${scope}" must be a concrete path or glob inside repair task allowedPaths/writeSet`,
149
240
  };
150
241
  }
151
- if (!scopeWithinPatterns(scope, allowed) || !scopeWithinPatterns(scope, writeSet)) {
242
+ if (!scopeWithinPatterns(scope, allowed) ||
243
+ !scopeWithinPatterns(scope, writeSet)) {
152
244
  return {
153
245
  ok: false,
154
246
  reason: `fixScope "${scope}" is outside repair task allowedPaths/writeSet`,
@@ -255,12 +347,16 @@ export function resolveRepairTaskForGate(input) {
255
347
  };
256
348
  }
257
349
  export function formatRepairArtifactForPrompt(artifact) {
350
+ const disposition = artifact.disposition ?? "repairable";
351
+ const requiredScope = artifact.requiredScope ?? [];
258
352
  return [
259
353
  "Repair artifact:",
260
354
  `- verdict: ${artifact.verdict}`,
261
355
  `- failureClass: ${artifact.failureClass}`,
262
356
  `- rootCause: ${artifact.rootCause}`,
357
+ `- disposition: ${disposition}`,
263
358
  `- fixScope: ${artifact.fixScope.length > 0 ? artifact.fixScope.join(", ") : "(none)"}`,
359
+ `- requiredScope: ${requiredScope.length > 0 ? requiredScope.join(", ") : "(none)"}`,
264
360
  `- invariant: ${artifact.invariant}`,
265
361
  `- evidenceRefs: ${artifact.evidenceRefs.length > 0 ? artifact.evidenceRefs.join(", ") : "(none)"}`,
266
362
  ].join("\n");
@@ -254,7 +254,10 @@ export function computeResetClosure(spec, effectiveFromNodeId) {
254
254
  if (!tasks.has(effectiveFromNodeId)) {
255
255
  return [];
256
256
  }
257
- return [effectiveFromNodeId, ...collectReachableDescendants(spec, effectiveFromNodeId)].sort();
257
+ return [
258
+ effectiveFromNodeId,
259
+ ...collectReachableDescendants(spec, effectiveFromNodeId),
260
+ ].sort();
258
261
  }
259
262
  export function assessResetClosureSafety(spec, resetNodeIds) {
260
263
  const tasks = taskById(spec);
@@ -355,7 +358,8 @@ function computeVerificationPolicy(spec, parentRunId, resetNodeIds, importedNode
355
358
  function deriveSuggestedAction(input) {
356
359
  if (input.eligible)
357
360
  return "dag-rerun";
358
- if (input.workerManaged || input.reasonCodes.includes("worker-managed-rerun-unsupported")) {
361
+ if (input.workerManaged ||
362
+ input.reasonCodes.includes("worker-managed-rerun-unsupported")) {
359
363
  return "worker-task-retry";
360
364
  }
361
365
  if (input.parentLifecycle === "paused")
@@ -449,7 +453,8 @@ export async function evaluateDagRerunPlan(input) {
449
453
  input.currentControllerFingerprint === input.parentControllerFingerprint;
450
454
  const workspaceMatched = input.parentTerminalWorkspace?.fingerprint !== undefined &&
451
455
  input.currentWorkspace?.fingerprint !== undefined &&
452
- input.parentTerminalWorkspace.fingerprint === input.currentWorkspace.fingerprint;
456
+ input.parentTerminalWorkspace.fingerprint ===
457
+ input.currentWorkspace.fingerprint;
453
458
  const skillSnapshotVerified = input.skillSnapshotOk !== false;
454
459
  const sourceBindingMatched = input.bindingStatus?.sourceBindingMatched ?? true;
455
460
  const taskContractBindingMatched = input.bindingStatus?.taskContractBindingMatched ?? true;
@@ -499,7 +504,8 @@ export async function evaluateDagRerunPlan(input) {
499
504
  blockedReasons.push("workspace-checkpoint-missing");
500
505
  }
501
506
  else if (input.currentWorkspace?.fingerprint &&
502
- input.parentTerminalWorkspace.fingerprint !== input.currentWorkspace.fingerprint) {
507
+ input.parentTerminalWorkspace.fingerprint !==
508
+ input.currentWorkspace.fingerprint) {
503
509
  reasonCodes.push("workspace-drift");
504
510
  blockedReasons.push("workspace-drift");
505
511
  }
@@ -566,9 +572,10 @@ export async function evaluateDagRerunPlan(input) {
566
572
  const totalPi = countExecutorNodes(input.parentSpec, allNodeIds, "pi");
567
573
  const resetPi = countExecutorNodes(input.parentSpec, resetNodeIds, "pi");
568
574
  const resetShell = countExecutorNodes(input.parentSpec, resetNodeIds, "shell");
569
- const workerAssociation = input.workerAssociation ?? (input.workerManaged
570
- ? { kind: "worker-managed" }
571
- : { kind: "standalone" });
575
+ const workerAssociation = input.workerAssociation ??
576
+ (input.workerManaged
577
+ ? { kind: "worker-managed" }
578
+ : { kind: "standalone" });
572
579
  const planWithoutHash = {
573
580
  schemaVersion: 1,
574
581
  eligible,
@@ -391,6 +391,12 @@ export const dagShellConfigSchema = z.object({
391
391
  repairArtifactGate: dagRepairArtifactGateSchema.optional(),
392
392
  /** fail (default): any nonzero command fails the node. record: finish node FINISHED with failure facts for downstream assess/repair. */
393
393
  nonZeroExitPolicy: z.enum(["fail", "record"]).optional(),
394
+ /**
395
+ * When true/omitted, stop after the first failed command (runtime default true).
396
+ * When false, continue executing remaining commands and aggregate failures.
397
+ * Optional on the schema so existing shell literals stay valid; executor applies default.
398
+ */
399
+ failFast: z.boolean().optional(),
394
400
  envAllowlist: z.array(z.string()).optional(),
395
401
  timeoutMs: z.number().optional(),
396
402
  cwd: z.string().optional(),
@@ -94,11 +94,47 @@ function computeFingerprint(input) {
94
94
  gitHeadSha: input.gitHeadSha,
95
95
  statusPorcelainSha256: input.statusPorcelainSha256,
96
96
  changedPaths: input.changedPaths,
97
+ stableGovernanceInputs: input.stableGovernanceInputs,
97
98
  });
98
99
  return sha256Hex(payload);
99
100
  }
101
+ /**
102
+ * Stable Task Contract governance inputs for same-run hard-verify reuse.
103
+ * Explicitly excludes `.harness/dag-runs/**` and other runner-owned volatile facts.
104
+ */
105
+ export function listStableGovernanceInputPaths(taskId) {
106
+ const safeTaskId = String(taskId ?? "").trim();
107
+ if (!safeTaskId ||
108
+ safeTaskId.includes("..") ||
109
+ safeTaskId.includes("/") ||
110
+ safeTaskId.includes("\\")) {
111
+ return [];
112
+ }
113
+ const base = `.harness/tasks/${safeTaskId}`;
114
+ return [
115
+ `${base}/task.json`,
116
+ `${base}/dag.json`,
117
+ `${base}/source/需求.md`,
118
+ `${base}/source/执行约束.md`,
119
+ `${base}/source/source-manifest.json`,
120
+ ];
121
+ }
122
+ async function hashStableGovernanceInputs(repoRoot, taskId) {
123
+ const paths = listStableGovernanceInputPaths(taskId);
124
+ const entries = [];
125
+ for (const relPath of paths) {
126
+ const hashed = await hashWorkspacePath(repoRoot, relPath, "modified");
127
+ entries.push({
128
+ path: relPath,
129
+ contentSha256: hashed.contentSha256 ?? sha256Hex(`missing:${relPath}`),
130
+ });
131
+ }
132
+ entries.sort((a, b) => a.path.localeCompare(b.path));
133
+ return entries;
134
+ }
100
135
  /**
101
136
  * Capture a workspace checkpoint for the current working tree.
137
+ * When `taskId` is provided, fingerprint includes stable ignored Task Contract inputs.
102
138
  */
103
139
  export async function captureWorkspaceCheckpoint(repoRoot, options) {
104
140
  const capturedAt = (options?.now ?? new Date()).toISOString();
@@ -127,10 +163,15 @@ export async function captureWorkspaceCheckpoint(repoRoot, options) {
127
163
  });
128
164
  }
129
165
  changedPaths.sort((a, b) => a.path.localeCompare(b.path));
166
+ const taskId = options?.taskId?.trim();
167
+ const stableGovernanceInputs = taskId
168
+ ? await hashStableGovernanceInputs(repoRoot, taskId)
169
+ : undefined;
130
170
  const fingerprint = computeFingerprint({
131
171
  gitHeadSha,
132
172
  statusPorcelainSha256,
133
173
  changedPaths,
174
+ stableGovernanceInputs,
134
175
  });
135
176
  return {
136
177
  schemaVersion: 1,
@@ -138,6 +179,7 @@ export async function captureWorkspaceCheckpoint(repoRoot, options) {
138
179
  gitHeadSha,
139
180
  statusPorcelainSha256,
140
181
  changedPaths,
182
+ ...(stableGovernanceInputs ? { stableGovernanceInputs } : {}),
141
183
  fingerprint,
142
184
  };
143
185
  }
@@ -29,7 +29,7 @@
29
29
  主会话(含 openCode、Cursor Chat、其他宿主 agent)= **operator-only**;skills 与本文件是纪律文档,**不能**替代 `task.json` / DAG `writeSet` / runtime 执法。
30
30
 
31
31
  | 类别 | 规则 |
32
- |---|---|
32
+ | --- | --- |
33
33
  | **允许** | 已发布 `loop-agent` / `agent-worker` CLI;只读 status/doctor/report/inspect/observe;准备 `source/*` 与 `task.json` 边界;human gate;shell 验证与 handoff。 |
34
34
  | **禁止** | 绕过 CLI 用宿主 Edit/Write/ApplyPatch 直接改业务实现;CLI/DAG 失败后「救火改文件」;用聊天自述代替 shell 验证。 |
35
35
  | **失败时** | `dag doctor` / `dag report` / `dag reconcile-run`(及适用 worker reconcile);修正任务源/`task.json`/DAG 后经 CLI 重跑。 |
@@ -40,7 +40,7 @@
40
40
  ### 自然语言入口路由
41
41
 
42
42
  | 用户表达 | 入口 | 执行动作 |
43
- |---|---|---|
43
+ | --- | --- | --- |
44
44
  | loop-agent 初始化 / loop agent 初始化 / loop agent初始化 / 初始化 loop-agent | 初始化 | 完成确定性初始化闭环 |
45
45
  | 初始化更新校验 / 检查初始化更新 / loop-agent 初始化更新校验 / loop agent初始化更新校验 | 更新校验 | 只读报告,不写入 |
46
46
  | 初始化对齐 / 升级后对齐 / reconcile 初始化 / loop-agent 初始化对齐 | 升级对齐 | 自动应用确定性安全动作;surface 缺失、人工决策、活跃 DAG/Worker 或 Worker 状态无法确认时零写入 |
@@ -127,7 +127,7 @@ agent-worker console serve --repo . --port 8790 # 兼容入口,等价于上
127
127
 
128
128
  live run 先用 `loop-agent dag status --run-id <run-id>` 看 lifecycle 与 liveness;用 `loop-agent dag report --run-id <run-id> --markdown` 读 facts;失败/paused 用 `loop-agent dag doctor --run-id <run-id> --markdown`。生命周期对齐先只读运行 `loop-agent dag reconcile-run --run-id <run-id>`;只有 runner 已停止且 operator 明确提供 `--action supersede|abandon --reason "..."` 时才允许变更。失败 run 用 `loop-agent dag closeout-draft --run-id <run-id>` 生成 failure handoff,不要写成成功 closeout。
129
129
 
130
- Runner heartbeat 只证明 lease;Provider/tool/output 活动才代表 meaningful progress,不得仅凭运行时长结束节点。恢复:status/doctor/report → classify → reconcile/replan → CLI 重跑 → shell verify。**禁止**把主会话直接 Edit 业务代码当作恢复手段。
130
+ Operator 须持续监控 live run,直到 controller 报告 run 已结束(节点/流程终态如 `FINISHED`、`FAILED` 或 `partial_failed`),或 Decision Gate **需要 approve**;不要在节点仍运行时假定完成。判活须组合 runner heartbeat、session events 与 `dag doctor` liveness/provider meaningful progress;Runner heartbeat 只证明 lease,alone ≠ progress,不得仅凭运行时长结束节点。exclusive writer(如 `implement-pi` / `repair-pi`)运行期间:主会话与其他 writer **不得并发修改工作区**,以免 write-guard 错误归因;只读 status/doctor/report/observe approve/reject/resume CLI 仍允许。恢复:status/doctor/report → classify → reconcile/replan → CLI 重跑 → shell verify。**禁止**把主会话直接 Edit 业务代码当作恢复手段。
131
131
 
132
132
  ### 运行态与验证
133
133
 
package/harness.json CHANGED
@@ -59,7 +59,7 @@
59
59
  "pi": {
60
60
  "description": "Pi planning, review, diagnosis, and bounded writing when DAG toolProfile=write",
61
61
  "LOW": "minimax-m3",
62
- "MED": {"model": "deepseek/deepseek-v4-flash","thinking": "max"},
62
+ "MED": "grok-4.5",
63
63
  "HIGH": "gpt-5.6-sol"
64
64
  }
65
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.26.3",
3
+ "version": "0.26.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -15,7 +15,7 @@ main session 是 decision-maker 与 scheduler,**不是** implementer。稀缺
15
15
  ## 默认执行模型
16
16
 
17
17
  | Actor | 主角色 | 避免 |
18
- |---|---|---|
18
+ | --- | --- | --- |
19
19
  | Main session (Operator Assist) | Objective、contract、**CLI 编排**、DAG review、routing、failure triage(doctor/reconcile)、approve/reject/resume、shell verify、handoff | 成为 coder;绕过 CLI 写业务树;失败后直接 Edit |
20
20
  | Agent DAG runner | 可恢复 multi-node orchestration、write policy、run artifacts | 不更新 DAG/source 的 ad-hoc replanning |
21
21
  | Cursor one-shot prompt | 显式有界诊断/介入;非默认 | 当 DAG executor、自动写入、无 path scope 宽写、失败默认恢复 |
@@ -48,7 +48,7 @@ sidecar intervention 是一次性 Pi 或 Cursor prompt,用于 unblock 主 work
48
48
  典型 routing:
49
49
 
50
50
  | 情况 | 使用 |
51
- |---|---|
51
+ | --- | --- |
52
52
  | 需 quick root-cause analysis、plan critique、log 解读 | one-shot Pi prompt,read-only |
53
53
  | 需 codebase-indexed multi-file 诊断或 bounded patch | one-shot Cursor prompt |
54
54
  | 需确定性 evidence | shell command / shell DAG node |
@@ -94,14 +94,20 @@ P5 验证:future agent 可从 practice report + completed node artifacts 继
94
94
  main session 编排;不是默认 implementer。in-flight run 期间:
95
95
 
96
96
  | Action | 何时 | 记录位置 |
97
- |--------|------|----------|
97
+ | -------- | ------ | ---------- |
98
98
  | Inspect status / node artifacts | 始终允许 | progress 或 sidecar output 中的 notes |
99
99
  | Sidecar read-only Pi/Cursor prompt | 诊断、plan critique、log 解读 | resume 前的 findings |
100
- | Surgical patch | small、obvious、可 verify;无 active `writeSet` 冲突 | `ai_workspace/loop-agent/reports/` 或 exec plan,含 scope + verification |
101
- | DAG/source repair | topology、writeSet 或 prompt contract 错误 | 编辑平台临时目录中的 DAG 或 plan;re-validate;rerun |
100
+ | Surgical patch | **不适用于** exclusive writer(如 `implement-pi` / `repair-pi`)active 期间的工作区写;仅当无 active exclusive writer、变更极窄且可 verify 的治理元数据时才可考虑 | `ai_workspace/loop-agent/reports/` 或 exec plan,含 scope + verification |
101
+ | DAG/source repair | topology、writeSet 或 prompt contract 错误;writer 非 active 时修正元数据 | 编辑平台临时目录中的 DAG 或 plan;re-validate;rerun |
102
102
  | Approve/reject/resume | Decision Gate `pause-on-human` | 仅 CLI artifacts |
103
103
  | Post-DAG closeout | promotion、report、plan archive、indexes | `promote-run`、`closeout task`、`ai_workspace/loop-agent/reports/`、`ai_workspace/loop-agent/progress`、exec-plan indexes — 非 root `artifacts/`,除非 explicit narrow writeSet |
104
104
 
105
+ **Live-run 纪律(与目标项目 AGENTS managed block 同口径)**:
106
+
107
+ - Operator 须持续监控 run,直到 controller 报告 run 已结束(节点/流程终态如 `FINISHED`、`FAILED` 或 `partial_failed`),或 Decision Gate **需要 approve**;不要在节点仍运行时假定完成。
108
+ - 判活须组合 runner heartbeat、session events 与 `dag doctor` liveness/provider meaningful progress;Runner heartbeat 只证明 lease,alone ≠ progress。
109
+ - exclusive writer 运行期间:主会话与其他 writer **不得并发修改工作区**,以免 write-guard 错误归因;只读 status/doctor/report/observe/console 与 approve/reject/resume CLI 仍允许。
110
+
105
111
  **Verdict 与 Decision Gate 提醒**(authoring guidance,非 runtime 变更):
106
112
 
107
113
  - `shell.verdictGate` 后的 review/supervisor node:首条非空行须精确为 `VERDICT: pass` 或 `VERDICT: request-revision`(P2/P4)。
@@ -117,7 +123,7 @@ main session 编排;不是默认 implementer。in-flight run 期间:
117
123
  1. 变更不触及业务功能逻辑;通常是 task source、DAG JSON typo、doc index、或删除 scratch。
118
124
  2. 原因已知;不需要 broad system understanding。
119
125
  3. 不改变 product requirement 语义、architecture、public API、data model 或 cross-platform contract。
120
- 4. 不与 active DAG exclusive `writeSet` 冲突。
126
+ 4. 不与 active DAG exclusive `writeSet` 冲突;且 exclusive writer 运行期间**一律禁止**主会话/其他 writer 并发改工作区(不仅是路径重叠判断),以免 write-guard 错误归因。
121
127
  5. 可立即 shell verify。
122
128
  6. 记录 scope + verification;且**下一步仍是 CLI re-validate / rerun**,不是「主会话继续实现」。
123
129
 
@@ -134,6 +140,7 @@ main session 编排;不是默认 implementer。in-flight run 期间:
134
140
  - 手工按计划实现功能或修测试失败。
135
141
  - 改 API/contract 语义或 refactor 子系统。
136
142
  - 编辑 in-flight exclusive DAG node 拥有的实现文件。
143
+ - exclusive writer active 时主会话或其他 writer 并发修改工作区(即使自认无 writeSet 冲突)。
137
144
  - 用 `cursor-prompt` / 宿主 Write 代替 `implement-pi` / `repair-pi`。
138
145
 
139
146
  失败默认序列(替代旧 surgical-patch 心智):