@tea-agent/loop-agent 0.36.1 → 0.36.3-beta.0

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.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # 更新日志
2
2
 
3
- ## [Unreleased]
3
+ ## [0.36.3-beta.0] - 2026-08-16
4
+
5
+ > 前端 DAG Mock 治理收紧(beta,发布到 `beta` dist-tag)。
6
+
7
+ ### 改进
8
+
9
+ - 前端 DAG 的 Mock 策略约束注入设计评审链:`mockContextBlock`(含冻结 allowlist 的「HARD CONSTRAINT」与修复路径)现在也注入 `frontend-design-review-pi` / `frontend-plan-revision-pi` / `frontend-final-design-review-pi`,消除「design-review 按项目规范要求 native、但生成期 allowlist 冻结为 not-needed、prewrite 硬阻断」的策略断环——评审链会明确提示这是生成期契约缺口(需声明 `frontendMock.verifyCommands` 或 `policy: "required"` 后重新生成 DAG)
10
+ - 前端任务生成期 Mock 预警:新增 `DagSpec.advisories`,任务源提到接口/API/Mock 但 auto 模式把策略收窄为 `not-needed` 时,`task advance` 的 `warnings` 会给出可操作提示(声明 `frontendMock.verifyCommands` 或 `policy: required` 后重新生成 DAG),把契约缺口提前到生成期暴露
4
11
 
5
12
  ## [0.36.1] - 2026-08-16
6
13
 
@@ -13,6 +20,27 @@
13
20
  - move interrupt eligibility to observability to honor kernel import boundary
14
21
  - retry semantic intake across Pi tiers with 300s timeout
15
22
 
23
+ ## [0.36.1-beta.0] - 2026-08-16
24
+
25
+ > 标准 DAG planner 输出超长紧凑重试(beta,发布到 `beta` dist-tag)。
26
+
27
+ ### 改进
28
+
29
+ - 标准 DAG 的 planner 节点(`contract-pi` / `plan-pi`)输出超长(`output-too-large`)时自动紧凑重试:新增 `PLANNER_OUTPUT_LIMIT_RETRY_POLICY`(默认重试集合 + `output-too-large`),紧凑重试指令按节点类型区分——结构化契约节点只返回单个紧凑 JSON,普通计划节点返回最小计划(有序步骤、writeSet 边界、验证命令),并丢弃上游原文引用与长证据摘录
30
+
31
+ ## [0.35.4-beta.0] - 2026-08-15
32
+
33
+ > frontend prewrite 阻断误路由修正 + Mock 契约文档落地(beta,发布到 `beta` dist-tag)。
34
+
35
+ ### 改进
36
+
37
+ - prewrite 阻断导致的 writer `SKIPPED`(`skippedReason=frontend-prewrite-not-authorized`)失败路由从 `DependencyFailure`/`unblock dependency` 修正为 `ContractMismatch`/`fix-frontend-task-contract-and-regenerate-dag`;`dag report` / `dag doctor` 透传 `skippedReason`,对「Mock 策略契约缺陷需改 task.json + 重新生成 DAG」这类场景给出可操作建议,而非误导性的「解除依赖」
38
+
39
+ ### 文档
40
+
41
+ - `docs/templates/frontend-task-constraints.md` 的 Mock 约束从 TODO 改为可落地契约:要求从项目**探索**实际存在的确定性 Mock 验证命令(不硬编码命令名)并声明到 `task.json.frontendMock.verifyCommands`,说明生成期 `allowedMockStrategies` 来源、`mock-strategy-outside-allowed` 是生成期契约问题(修复 = 补契约 + 重新生成 DAG,plan-revision 无法修复)
42
+ - `docs/runtime/frontend-implementation-workflow.md` 的 task.json 输入标准补充 Mock 契约声明、命令探索与失败恢复指引
43
+
16
44
  ## [0.36.0] - 2026-08-15
17
45
 
18
46
  ### 新增
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.36.1",
4
- "gitSha": "e023c82feae11ad8a6f5f9e50e8553cc38512f5e",
5
- "builtAt": "2026-08-16T09:24:03.417Z"
3
+ "version": "0.36.3-beta.0",
4
+ "gitSha": "5baa4176d2b96e68c61f759934c6582911b4a42a",
5
+ "builtAt": "2026-08-16T11:51:43.878Z"
6
6
  }
@@ -44,7 +44,9 @@ function routeToProductLine(input) {
44
44
  case "decision-envelope":
45
45
  return "NeedsHuman";
46
46
  case "skipped":
47
- return "DependencyFailure";
47
+ return input.skippedReason === "frontend-prewrite-not-authorized"
48
+ ? "ContractMismatch"
49
+ : "DependencyFailure";
48
50
  case "shell-command":
49
51
  if (/\b(ENOENT|PATH|command not found|No such file|not found in PATH|bash: .*: No such file)\b/i.test(raw)) {
50
52
  return "EnvFailure";
@@ -90,9 +92,12 @@ export function routeDagFailure(input) {
90
92
  if (!productLineFailureCategory)
91
93
  return {};
92
94
  const recommendedFollowUp = productLineFailureCategory === "ContractMismatch" &&
93
- isFrontendDesignGateNode(input.nodeId ?? "")
94
- ? "frontend-plan-revision-and-rerun"
95
- : FOLLOW_UP_BY_PRODUCT_LINE[productLineFailureCategory];
95
+ input.skippedReason === "frontend-prewrite-not-authorized"
96
+ ? "fix-frontend-task-contract-and-regenerate-dag"
97
+ : productLineFailureCategory === "ContractMismatch" &&
98
+ isFrontendDesignGateNode(input.nodeId ?? "")
99
+ ? "frontend-plan-revision-and-rerun"
100
+ : FOLLOW_UP_BY_PRODUCT_LINE[productLineFailureCategory];
96
101
  return {
97
102
  productLineFailureCategory,
98
103
  recommendedFollowUp,
@@ -9,7 +9,7 @@ import { planMavenVerification, } from "../../verification/maven/index.js";
9
9
  import { pathMatchesPattern } from "../../shared/git-progress.js";
10
10
  import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
11
11
  import { buildDecisionEnvelopePromptContract } from "./decision-envelope.js";
12
- import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY, WRITER_TRANSPORT_RETRY_POLICY, isSafeReadOnlyPiRetryCandidate, isWriterTransportRetryCandidate, } from "./retry-policy.js";
12
+ import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PLANNER_OUTPUT_LIMIT_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY, WRITER_TRANSPORT_RETRY_POLICY, isSafeReadOnlyPiRetryCandidate, isWriterTransportRetryCandidate, } from "./retry-policy.js";
13
13
  import { REVIEW_JSON_VERDICT_OUTPUT_PROTOCOL, REVIEW_VERDICT_OUTPUT_PROTOCOL, } from "./output-protocol.js";
14
14
  import { resolveAdapter } from "../../adapters/index.js";
15
15
  import { loadHarnessManifest } from "../../governance/harness.js";
@@ -558,6 +558,39 @@ export function hasApiDependency(sources) {
558
558
  .some((clause) => !negationPatterns.some((pattern) => pattern.test(clause)) &&
559
559
  dependencyPatterns.some((pattern) => pattern.test(clause)));
560
560
  }
561
+ /**
562
+ * Generation-time heuristic: does the task source mention interface/API/Mock
563
+ * needs anywhere (requirement or constraints)?
564
+ *
565
+ * Mirrors hasApiDependency's clause-level negation filtering so explicit
566
+ * "not involved / not implementing" clauses and out-of-scope sections do not
567
+ * become positive evidence. Used to emit a machine-visible advisory when auto
568
+ * mode narrows Mock to not-needed despite the source mentioning such needs.
569
+ */
570
+ export function frontendSourceMentionsMock(sources) {
571
+ const text = [sources.requirementMarkdown, sources.constraintMarkdown]
572
+ .filter((entry) => typeof entry === "string" && entry.trim().length > 0)
573
+ .map((entry) => normalizeTaskRequirementText(entry).replace(/`[^`\n]*`/g, " "))
574
+ .join("\n");
575
+ const signalPatterns = [
576
+ /mock/i,
577
+ /模拟服务/,
578
+ /接口桩/,
579
+ /\bmsw\b/i,
580
+ /(?<![A-Za-z0-9_])API(?![A-Za-z0-9_])/i,
581
+ /接口/,
582
+ ];
583
+ const negationPatterns = [
584
+ /(?:不涉及|无需|不需要|不依赖|不调用|不请求|不实现|没有|禁止|不得).{0,16}(?:接口|后端|服务端|远程数据|异步数据|API|mock|模拟|桩)/i,
585
+ /\b(?:no|without|does\s+not|do\s+not|must\s+not)\b.{0,24}\b(?:api|endpoint|request|backend|server|mock)\b/i,
586
+ ];
587
+ return text
588
+ .split(/[。!?!?;;,,\r\n]+/)
589
+ .map((clause) => clause.trim())
590
+ .filter(Boolean)
591
+ .some((clause) => !negationPatterns.some((pattern) => pattern.test(clause)) &&
592
+ signalPatterns.some((pattern) => pattern.test(clause)));
593
+ }
561
594
  /**
562
595
  * Resolve frontend Mock mode from capability seed, task config, and interface dependency analysis.
563
596
  *
@@ -2111,6 +2144,7 @@ function resolveFrontendMockContextBlock(sources) {
2111
2144
  parts.push("Generation-time evidence does not require Mock. The assessment must still use contract/scout evidence: select not-needed when Mock is intentionally skipped, or select a safe Mock strategy if project evidence supports one.");
2112
2145
  if (frontendMockStrategyMustBeNotNeeded(sources)) {
2113
2146
  parts.push('Auto mode has no confirmed project Mock capability or no deterministic Mock verification command. The structured contract must set mockApi.strategy to "not-needed". Do not add Mock files or dependencies; keep the real request path as default and record any unproved backend behavior as Real Integration Gap.');
2147
+ parts.push('HARD CONSTRAINT (frozen at generation time): this DAG allows only mockApi.strategy "not-needed"; the prewrite gate rejects any other strategy. If project governance (openspec / ai_workspace / decision records, e.g. a DEC rule requiring native) demands Mock-backed verification, that is a generation-time contract gap, not a plan-revision defect: declare frontendMock.verifyCommands (or policy: "required") in task.json and regenerate the DAG. Do not emit any mockApi.strategy outside the allowlist and do not add Mock files or dependencies within this run.');
2114
2148
  }
2115
2149
  }
2116
2150
  if (mode === "blocked") {
@@ -2509,6 +2543,11 @@ async function buildFrontendHybridDagFromTask(sources) {
2509
2543
  `- Behavior command source: ${behaviorVerifyEvidence.commandSource}`,
2510
2544
  ...behaviorVerifyEvidence.commandLabels.map((command) => ` - ${JSON.stringify(command)}`),
2511
2545
  ].join("\n");
2546
+ const advisories = [];
2547
+ if (frontendSourceMentionsMock(frontendSources) &&
2548
+ frontendMockStrategyMustBeNotNeeded(frontendSources)) {
2549
+ advisories.push("auto 模式已将 Mock 策略收窄为 not-needed:任务源提到接口/API/Mock 需求,但仓库无确认 Mock 能力或无确定性 Mock 验证命令。若项目规范要求 Mock,请声明 frontendMock.verifyCommands 或 policy:required 后重新生成 DAG。");
2550
+ }
2512
2551
  const spec = {
2513
2552
  version: 3,
2514
2553
  title: `Frontend implementation DAG: ${taskConfig.title}`,
@@ -2523,6 +2562,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2523
2562
  skillsByRole: FRONTEND_SKILLS_BY_ROLE,
2524
2563
  executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS,
2525
2564
  verifyStrategy: resolveDagVerifyStrategy(taskConfig),
2565
+ advisories: advisories.length > 0 ? advisories : undefined,
2526
2566
  tasks: [
2527
2567
  {
2528
2568
  id: "frontend-contract-pi",
@@ -2617,6 +2657,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2617
2657
  "Read-only: do not modify repository files.",
2618
2658
  fixedVerificationContext,
2619
2659
  sourceContext,
2660
+ mockContextBlock,
2620
2661
  ].join("\n\n"),
2621
2662
  },
2622
2663
  {
@@ -2651,6 +2692,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2651
2692
  fixedVerificationContext,
2652
2693
  sourceContext,
2653
2694
  frontendContractSchemaBlock,
2695
+ mockContextBlock,
2654
2696
  ].join("\n\n"),
2655
2697
  },
2656
2698
  {
@@ -2681,6 +2723,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2681
2723
  "Read-only: do not modify repository files.",
2682
2724
  fixedVerificationContext,
2683
2725
  sourceContext,
2726
+ mockContextBlock,
2684
2727
  ].join("\n\n"),
2685
2728
  },
2686
2729
  {
@@ -5783,7 +5826,9 @@ function applyDefaultReadOnlyRetryPolicy(spec) {
5783
5826
  if (isSafeReadOnlyPiRetryCandidate(task)) {
5784
5827
  task.retryPolicy = task.outputProtocol
5785
5828
  ? PROTOCOL_AWARE_PI_RETRY_POLICY
5786
- : DEFAULT_READ_ONLY_PI_RETRY_POLICY;
5829
+ : task.role === "planner"
5830
+ ? PLANNER_OUTPUT_LIMIT_RETRY_POLICY
5831
+ : DEFAULT_READ_ONLY_PI_RETRY_POLICY;
5787
5832
  continue;
5788
5833
  }
5789
5834
  if (isWriterTransportRetryCandidate(task)) {
@@ -743,6 +743,7 @@ function findDoctorFailureNode(state) {
743
743
  nodeId: state.pausedByNodeId,
744
744
  status: node?.status,
745
745
  rawFailureCategory: node?.failureCategory,
746
+ skippedReason: node?.skippedReason,
746
747
  };
747
748
  }
748
749
  const errorEntry = Object.entries(state.nodes).find(([, node]) => node.status === "ERROR");
@@ -758,6 +759,7 @@ function findDoctorFailureNode(state) {
758
759
  nodeId: selected[0],
759
760
  status: selected[1].status,
760
761
  rawFailureCategory: selected[1].failureCategory,
762
+ skippedReason: selected[1].skippedReason,
761
763
  };
762
764
  }
763
765
  async function readRunOwnedBackendTestClassification(runDir) {
@@ -786,6 +788,7 @@ async function resolveDoctorFailureRouting(input) {
786
788
  normalizedFailureCategory: "unknown",
787
789
  nodeId: input.nodeId,
788
790
  productLineFailureCategory: classifiedCategory,
791
+ skippedReason: input.skippedReason,
789
792
  });
790
793
  }
791
794
  return routeDagFailure(input);
@@ -807,6 +810,7 @@ async function formatDagDoctorMarkdown(repoRoot, runId) {
807
810
  rawFailureCategory,
808
811
  normalizedFailureCategory: normalizedCategory,
809
812
  nodeId: failure.nodeId,
813
+ skippedReason: failure.skippedReason,
810
814
  });
811
815
  const evidence = failure.nodeId
812
816
  ? path.join(located.runDir, failure.nodeId, "result.summary.md")
@@ -216,18 +216,28 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
216
216
  "</retry_instruction>",
217
217
  ].join("\n");
218
218
  }
219
- if (task.outputMode !== "structured-required" ||
220
- previousFailureCategory !== "output-too-large") {
219
+ if (previousFailureCategory !== "output-too-large") {
221
220
  return basePrompt;
222
221
  }
222
+ if (task.outputMode === "structured-required") {
223
+ return [
224
+ basePrompt,
225
+ "",
226
+ "<retry_instruction>",
227
+ "Previous attempt exceeded the structured output size limit.",
228
+ "Return only the compact structured artifact required by this node's output contract.",
229
+ "Do not include explanatory prose, duplicated upstream context, long evidence excerpts, or additional markdown sections.",
230
+ "If a fenced JSON object is required, output exactly one fenced json block and nothing else.",
231
+ "</retry_instruction>",
232
+ ].join("\n");
233
+ }
223
234
  return [
224
235
  basePrompt,
225
236
  "",
226
237
  "<retry_instruction>",
227
- "Previous attempt exceeded the structured output size limit.",
228
- "Return only the compact structured artifact required by this node's output contract.",
229
- "Do not include explanatory prose, duplicated upstream context, long evidence excerpts, or additional markdown sections.",
230
- "If a fenced JSON object is required, output exactly one fenced json block and nothing else.",
238
+ "Previous attempt exceeded the output size limit.",
239
+ "Return a minimal plan: ordered steps, narrow writeSet boundaries, and verification commands.",
240
+ "Drop verbatim upstream quotes, long evidence excerpts, and repeated context.",
231
241
  "</retry_instruction>",
232
242
  ].join("\n");
233
243
  }
@@ -441,6 +441,7 @@ export async function buildDagRunReportEntry(input) {
441
441
  normalizedFailureCategory,
442
442
  nodeId,
443
443
  executor: node.executor,
444
+ skippedReason: node.skippedReason,
444
445
  });
445
446
  const followUp = node.status === "ERROR" || node.status === "SKIPPED"
446
447
  ? recommendFollowUpForFailureCategory(node.failureCategory)
@@ -505,6 +506,7 @@ export async function buildDagRunReportEntry(input) {
505
506
  normalizedFailureCategory: normalizeDagFailureCategory(pausedNode.failureCategory, pausedNode.status),
506
507
  failureCategory: pausedNode.failureCategory,
507
508
  nodeId: input.state.pausedByNodeId,
509
+ skippedReason: pausedNode.skippedReason,
508
510
  }
509
511
  : firstActionableNode
510
512
  ? {
@@ -513,6 +515,7 @@ export async function buildDagRunReportEntry(input) {
513
515
  normalizeDagFailureCategory(firstActionableNode.failureCategory, firstActionableNode.status),
514
516
  failureCategory: firstActionableNode.failureCategory,
515
517
  nodeId: firstActionableNode.nodeId,
518
+ skippedReason: input.state.nodes[firstActionableNode.nodeId]?.skippedReason,
516
519
  }
517
520
  : {
518
521
  status: input.state.status,
@@ -529,6 +532,9 @@ export async function buildDagRunReportEntry(input) {
529
532
  rawFailureCategory: runRecoverySource.failureCategory,
530
533
  normalizedFailureCategory: runRecoverySource.normalizedFailureCategory,
531
534
  nodeId: "nodeId" in runRecoverySource ? runRecoverySource.nodeId : undefined,
535
+ skippedReason: "skippedReason" in runRecoverySource
536
+ ? runRecoverySource.skippedReason
537
+ : undefined,
532
538
  });
533
539
  const runFollowUp = input.state.status === "failed" ||
534
540
  input.state.status === "partial_failed"
@@ -118,6 +118,17 @@ export const STRUCTURED_REQUIRED_PI_RETRY_POLICY = {
118
118
  ...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
119
119
  retryCategories: [...STRUCTURED_REQUIRED_DAG_RETRY_CATEGORIES],
120
120
  };
121
+ /**
122
+ * Planner read-only nodes (standard DAG contract-pi / plan-pi) may retry with
123
+ * a compact-output instruction when the model produced an oversized assistant
124
+ * response. Only `output-too-large` is added on top of the default set;
125
+ * report/review and other read-only roles keep the default set so they do not
126
+ * silently learn new output semantics.
127
+ */
128
+ export const PLANNER_OUTPUT_LIMIT_RETRY_POLICY = {
129
+ ...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
130
+ retryCategories: [...DEFAULT_DAG_RETRY_CATEGORIES, STRUCTURED_OUTPUT_RETRY_CATEGORY],
131
+ };
121
132
  /**
122
133
  * Default retry for reviewer / recovery nodes that declare outputProtocol.
123
134
  * Includes protocol-invalid so missing VERDICT lines are corrected in-node.
@@ -822,6 +822,8 @@ export const dagSpecSchema = z
822
822
  defaults: dagDefaultsSchema,
823
823
  skillsByRole: z.record(z.string(), z.array(z.string())).optional(),
824
824
  executorModels: dagExecutorModelsSchema.optional(),
825
+ /** Generation-time advisories surfaced as machine-visible warnings. */
826
+ advisories: z.array(z.string()).optional(),
825
827
  tasks: z.array(dagTaskSchema).min(1),
826
828
  })
827
829
  .superRefine((spec, ctx) => {
@@ -14,6 +14,7 @@ const GOVERNANCE_WARNING_TYPES = new Set([
14
14
  "shell-verdict-gate-multi-command-state",
15
15
  "shell-verdict-gate-handwritten-inline",
16
16
  "orphan-writer-source-binding",
17
+ "advisory",
17
18
  ]);
18
19
  const ROOT_ARTIFACT_PATH_PROBES = [
19
20
  "artifacts/修改记录.md",
@@ -32,10 +33,11 @@ export function collectGovernanceWarnings(issues) {
32
33
  return issues.filter(isGovernanceWarning);
33
34
  }
34
35
  function collectBlockingIssues(issues, options = {}) {
36
+ const nonAdvisory = issues.filter((issue) => issue.type !== "advisory");
35
37
  if (options.strictGovernance) {
36
- return [...issues];
38
+ return nonAdvisory;
37
39
  }
38
- return issues.filter((issue) => !isGovernanceWarning(issue));
40
+ return nonAdvisory.filter((issue) => !isGovernanceWarning(issue));
39
41
  }
40
42
  function normalizePath(value) {
41
43
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
@@ -956,6 +958,9 @@ export function validateDagSpec(spec) {
956
958
  validateSameRankWriteSetConflicts(spec, ranks, issues);
957
959
  validateSameRankAgentAttributionRisks(spec, ranks, issues);
958
960
  validateWriterSourceBinding(spec, issues);
961
+ for (const advisory of spec.advisories ?? []) {
962
+ issues.push({ type: "advisory", message: advisory });
963
+ }
959
964
  return issues;
960
965
  }
961
966
  function validateProjectGovernanceTaskConfig(task, spec, issues) {
@@ -18,13 +18,19 @@ TODO
18
18
 
19
19
  ## Mock 约束(数据型任务)
20
20
 
21
- - Mock/API/schema 规范路径:TODO
22
- - 既有 Mock service root、handler/fixture/bootstrap:TODO
23
- - 既有 browser/e2e interception request adapter/DI seam:TODO
24
- - 启动、健康检查和专项验证命令:TODO
25
- - production 禁用边界:TODO
26
- - 真实请求默认路径与 Mock 显式启用方式:TODO
27
- - `task.json.frontendMock.policy`:`auto | required | disabled`
21
+ 本段决定生成期 `allowedMockStrategies` 与冻结的 Mock 验证命令。只有当生成期**能证明任务确实包含 Mock 且有确定性验证命令**时,DAG 才会放行 `native` / `browser-intercept` / `request-adapter`;否则自动收敛为 `not-needed`,design review 若按项目规范改选 `native`,会在 prewrite 被 `mock-strategy-outside-allowed` 拦截、`implement` 被跳过。
22
+
23
+ - **何时必须填**:项目规范(`openspec/project-specs/**`、`ai_workspace/**`、mock 规则或类似 DEC-* 决策)要求/建议 Mock;或需求涉及远端接口且后端未就绪。规范要求 Mock 时,优先 `task.json.frontendMock.policy: "required"`。
24
+ - **命令要探索、不要硬编码**:到项目 `package.json` scripts 里找实际存在的 Mock 相关脚本(如 `mock`、`mock:*`、`dev:mock`,或名字含 mock 的脚本),结合既有 service root 的 handler/fixture/bootstrap 启动方式,确定一条**真实存在、确定、可自终止**(启动→断言→退出 0)的验证命令。常驻 dev server 必须包装成自终止脚本(start→assert→stop),否则 verify shell 会超时 fail-closed。
25
+ - **写入 task.json**:把探索到的命令原样写进 `frontendMock.verifyCommands`(`label` 唯一、`command` 与项目脚本一致)。`label` 会进入冻结命令集,implementation contract 的 `verificationTarget.commandLabel` 必须逐字引用它。
26
+ - **命令来源白名单**:只允许项目 `package.json` 已有脚本、Mock capability seed、或本段声明的 `verifyCommands`;plan/design 阶段不能发明 shell 命令。
27
+ - **Mock/API/schema 规范路径**:TODO
28
+ - **既有 Mock service root、handler/fixture/bootstrap**:TODO
29
+ - **既有 browser/e2e interception 或 request adapter/DI seam**:TODO
30
+ - **production 禁用边界**:TODO
31
+ - **真实请求默认路径与 Mock 显式启用方式**:TODO
32
+ - **`task.json.frontendMock.policy`**:`auto | required | disabled`(规范强制 Mock 用 `required`)
33
+ - **被拦截时怎么修**:`mock-strategy-outside-allowed` / `no authorized Mock verification commands` 是**生成期契约问题,不是 plan 问题**——补 `frontendMock.verifyCommands`(或 `policy: "required"` + 命令)后**重新生成 DAG** 再跑,plan-revision 无法修复它。
28
34
 
29
35
  ## allowedPaths
30
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.36.1",
3
+ "version": "0.36.3-beta.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",