@tea-agent/loop-agent 0.26.5-beta.0 → 0.26.5-beta.1

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.
@@ -346,12 +346,61 @@ function secretIssues(value, at = "$", issues = []) {
346
346
  }
347
347
  export function extractFrontendImplementationJson(text) {
348
348
  const trimmed = text.trim();
349
+ const parse = (source) => {
350
+ try {
351
+ return JSON.parse(source);
352
+ }
353
+ catch (error) {
354
+ // Models sometimes put ordinary ASCII quotes inside a JSON string
355
+ // (for example: `reason: "支持..."`). Repair only quotes that are
356
+ // clearly not structural: a closing quote is followed by JSON
357
+ // punctuation, while an embedded quote is followed by content.
358
+ let repaired = "";
359
+ let inString = false;
360
+ let escaped = false;
361
+ for (let index = 0; index < source.length; index += 1) {
362
+ const character = source[index];
363
+ if (character !== '"') {
364
+ repaired += character;
365
+ if (inString && character === "\\" && !escaped)
366
+ escaped = true;
367
+ else
368
+ escaped = false;
369
+ continue;
370
+ }
371
+ if (escaped) {
372
+ repaired += character;
373
+ escaped = false;
374
+ continue;
375
+ }
376
+ if (!inString) {
377
+ inString = true;
378
+ repaired += character;
379
+ continue;
380
+ }
381
+ const next = source.slice(index + 1).trimStart()[0];
382
+ if ([",", "}", "]", ":"].includes(next ?? "") || source.slice(index + 1).trim() === "") {
383
+ inString = false;
384
+ repaired += character;
385
+ }
386
+ else {
387
+ repaired += "\\\"";
388
+ }
389
+ }
390
+ try {
391
+ return JSON.parse(repaired);
392
+ }
393
+ catch {
394
+ throw error;
395
+ }
396
+ }
397
+ };
349
398
  if (trimmed.startsWith("{") && trimmed.endsWith("}"))
350
- return JSON.parse(trimmed);
399
+ return parse(trimmed);
351
400
  const blocks = [...trimmed.matchAll(/```json\s*\n([\s\S]*?)\n```/gi)];
352
401
  if (blocks.length !== 1)
353
402
  throw new Error("output must contain exactly one fenced json object");
354
- return JSON.parse(blocks[0][1]);
403
+ return parse(blocks[0][1]);
355
404
  }
356
405
  /**
357
406
  * Build the authoritative frontend-implementation-contract sourceBinding from
@@ -2455,9 +2455,38 @@ async function buildFrontendHybridDagFromTask(sources) {
2455
2455
  sourceContext,
2456
2456
  ].join("\n\n"),
2457
2457
  },
2458
+ {
2459
+ id: "frontend-contract-json-pi",
2460
+ depends_on: [
2461
+ "frontend-plan-revision-pi",
2462
+ "frontend-plan-pi",
2463
+ "frontend-final-design-review-pi",
2464
+ "frontend-design-review-pi",
2465
+ ],
2466
+ dependsPolicy: "all-or-condition-skip",
2467
+ role: "planner",
2468
+ executor: "pi",
2469
+ complexity: "MED",
2470
+ writePolicy: "read-only",
2471
+ outputMode: "structured-required",
2472
+ retryPolicy: STRUCTURED_REQUIRED_PI_RETRY_POLICY,
2473
+ allowedPaths: readOnlyPaths,
2474
+ forbiddenPaths,
2475
+ skills: FRONTEND_IMPLEMENTATION_SKILLS,
2476
+ outputContract: "Return exactly one JSON object conforming to frontend-implementation-contract-v1. No Markdown, prose, comments, or code fences.",
2477
+ subtask_prompt: [
2478
+ "Convert the effective reviewed frontend plan into the canonical frontend-implementation-contract-v1 JSON.",
2479
+ "Use frontend-plan-revision-pi when it is FINISHED; otherwise use frontend-plan-pi. Confirm the effective design review passed before producing the contract.",
2480
+ "Return only the JSON object. Do not wrap it in Markdown or a code fence. Do not add explanatory text.",
2481
+ "Preserve all requirement expectedOutcome, interaction trigger/expectedBehavior, target files, verification targets, Mock/API decisions, and Real Integration Gap from the effective plan.",
2482
+ frontendContractSchemaBlock,
2483
+ sourceContext,
2484
+ ].join("\n\n"),
2485
+ },
2458
2486
  {
2459
2487
  id: "frontend-prewrite-gate-shell",
2460
2488
  depends_on: [
2489
+ "frontend-contract-json-pi",
2461
2490
  "frontend-final-design-review-pi",
2462
2491
  "frontend-design-review-pi",
2463
2492
  "frontend-plan-revision-pi",
@@ -2476,8 +2505,8 @@ async function buildFrontendHybridDagFromTask(sources) {
2476
2505
  commands: [],
2477
2506
  frontendPrewriteGate: {
2478
2507
  schemaVersion: 1,
2479
- planFromNodeId: "frontend-plan-revision-pi",
2480
- planFallbackFromNodeIds: ["frontend-plan-pi"],
2508
+ planFromNodeId: "frontend-contract-json-pi",
2509
+ planFallbackFromNodeIds: ["frontend-plan-revision-pi", "frontend-plan-pi"],
2481
2510
  reviewFromNodeId: "frontend-final-design-review-pi",
2482
2511
  reviewFallbackFromNodeIds: ["frontend-design-review-pi"],
2483
2512
  requiredRequirementIds: requirementIds,
@@ -5287,7 +5316,7 @@ async function buildHybridDagForTemplate(sources, template) {
5287
5316
  else if (template === "review-gated-dag")
5288
5317
  spec = buildReviewGatedHybridDag(standard, sources);
5289
5318
  else
5290
- spec = buildSupervisedHybridDag(standard, sources);
5319
+ spec = await buildSupervisedHybridDag(standard, sources);
5291
5320
  }
5292
5321
  applyProjectGovernanceReview(spec, template, sources);
5293
5322
  // New generate path always emits DagSpec v4 + bindings.
@@ -5721,10 +5750,12 @@ function buildWriteSetGateNode(sources) {
5721
5750
  },
5722
5751
  };
5723
5752
  }
5724
- function buildSoftVerifyNode(sources) {
5753
+ async function buildSoftVerifyNode(sources) {
5725
5754
  const implementId = implementationNodeId();
5726
5755
  const strategy = resolveDagVerifyStrategy(sources.taskConfig, "1");
5727
- const fallbackCommands = ["npm run typecheck"];
5756
+ const fallbackCommands = sources.repoRoot
5757
+ ? (await discoverFrontendFallbackVerifyCommands(sources.repoRoot)).staticCommands
5758
+ : ["npm run typecheck"];
5728
5759
  const focusedIntermediate = sources.verifyCommands?.intermediate.filter((command) => !isFullSuiteVerifyCommand(command));
5729
5760
  const plannedIntermediate = applyMavenVerificationPlanning({
5730
5761
  repoRoot: sources.repoRoot,
@@ -5966,7 +5997,7 @@ function resolveSupervisedConvergence(taskConfig) {
5966
5997
  chainNodeIds: [...SUPERVISED_CONVERGENCE_CHAIN_NODE_IDS],
5967
5998
  };
5968
5999
  }
5969
- function buildSupervisedHybridDag(standard, sources) {
6000
+ async function buildSupervisedHybridDag(standard, sources) {
5970
6001
  const contract = getTaskOrThrow(standard, "contract-pi");
5971
6002
  const scoutSrc = getTaskOrThrow(standard, "scout-src");
5972
6003
  const scoutTests = getTaskOrThrow(standard, "scout-tests");
@@ -6018,7 +6049,7 @@ function buildSupervisedHybridDag(standard, sources) {
6018
6049
  "final-write-set-audit-format-repair-pi",
6019
6050
  ],
6020
6051
  }),
6021
- buildSoftVerifyNode(sources),
6052
+ await buildSoftVerifyNode(sources),
6022
6053
  buildProcessSupervisorNode(sources),
6023
6054
  buildProcessGateNode(sources),
6024
6055
  buildRepairNode(sources),
package/harness.json CHANGED
@@ -58,9 +58,9 @@
58
58
  "executors": {
59
59
  "pi": {
60
60
  "description": "Pi planning, review, diagnosis, and bounded writing when DAG toolProfile=write",
61
- "LOW": "minimax-m3",
62
- "MED": "grok-4.5",
63
- "HIGH": "gpt-5.6-sol"
61
+ "LOW": {"model": "deepseek/deepseek-v4-flash", "thinking": "high"},
62
+ "MED": {"model": "deepseek/deepseek-v4-flash", "thinking": "max"},
63
+ "HIGH": {"model": "deepseek/deepseek-v4-flash", "thinking": "max"}
64
64
  }
65
65
  }
66
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.26.5-beta.0",
3
+ "version": "0.26.5-beta.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",