@tea-agent/loop-agent 0.26.5-beta.0 → 0.26.5-beta.2
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,75 @@ 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
|
|
399
|
+
return parse(trimmed);
|
|
351
400
|
const blocks = [...trimmed.matchAll(/```json\s*\n([\s\S]*?)\n```/gi)];
|
|
352
|
-
if (blocks.length
|
|
401
|
+
if (blocks.length === 0)
|
|
353
402
|
throw new Error("output must contain exactly one fenced json object");
|
|
354
|
-
|
|
403
|
+
const candidates = [];
|
|
404
|
+
for (const block of blocks) {
|
|
405
|
+
try {
|
|
406
|
+
const candidate = parse(block[1]);
|
|
407
|
+
if (candidate && typeof candidate === "object" && !Array.isArray(candidate))
|
|
408
|
+
candidates.push(candidate);
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// Ignore incomplete model scratch blocks. A later complete contract
|
|
412
|
+
// block may still be deterministically recoverable.
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (candidates.length !== 1)
|
|
416
|
+
throw new Error(`output must contain exactly one valid fenced json object (found ${candidates.length})`);
|
|
417
|
+
return candidates[0];
|
|
355
418
|
}
|
|
356
419
|
/**
|
|
357
420
|
* Build the authoritative frontend-implementation-contract sourceBinding from
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { isOpenspecSpecFilePath } from "../../shared/openspec-spec.js";
|
|
4
|
-
import { frontendImplementationContractSchema, materializeFrontendImplementationContract, assertFrontendSourceBindingFresh, } from "./frontend-implementation-contract.js";
|
|
5
|
+
import { frontendImplementationContractSchema, FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, materializeFrontendImplementationContract, assertFrontendSourceBindingFresh, } from "./frontend-implementation-contract.js";
|
|
5
6
|
import { captureFrontendWorktreeBaseline } from "./frontend-worktree-diff.js";
|
|
6
7
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
7
8
|
async function selectNode(runDir, primary, fallbacks) {
|
|
@@ -160,13 +161,20 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
160
161
|
if (missingIds.length > 0) {
|
|
161
162
|
throw new Error(`frontend prewrite gate missing requirement ids: ${missingIds.join(", ")}`);
|
|
162
163
|
}
|
|
163
|
-
const
|
|
164
|
+
const artifactPath = path.join(input.runDir, input.config.outputDir, input.config.artifactName);
|
|
165
|
+
const artifact = await stat(artifactPath)
|
|
166
|
+
.then(async () => ({
|
|
167
|
+
path: artifactPath,
|
|
168
|
+
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
169
|
+
sha256: createHash("sha256").update(await readFile(artifactPath)).digest("hex"),
|
|
170
|
+
}))
|
|
171
|
+
.catch(() => materializeFrontendImplementationContract({
|
|
164
172
|
runDir: input.runDir,
|
|
165
173
|
fromNodeId: planNodeId,
|
|
166
174
|
artifactName: input.config.artifactName,
|
|
167
175
|
outputDir: input.config.outputDir,
|
|
168
176
|
sourceBinding: input.sourceBinding,
|
|
169
|
-
});
|
|
177
|
+
}));
|
|
170
178
|
const raw = JSON.parse(await readFile(artifact.path, "utf8"));
|
|
171
179
|
const contract = frontendImplementationContractSchema.parse(raw);
|
|
172
180
|
if (!input.config.allowedMockStrategies.includes(contract.mockApi.strategy)) {
|
|
@@ -2455,9 +2455,62 @@ 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
|
+
},
|
|
2486
|
+
{
|
|
2487
|
+
id: "frontend-contract-json-validate-shell",
|
|
2488
|
+
depends_on: ["frontend-contract-json-pi"],
|
|
2489
|
+
role: "verifier",
|
|
2490
|
+
executor: "shell",
|
|
2491
|
+
complexity: "LOW",
|
|
2492
|
+
writePolicy: "read-only",
|
|
2493
|
+
allowedPaths: readOnlyPaths,
|
|
2494
|
+
forbiddenPaths,
|
|
2495
|
+
outputContract: "Validated frontend implementation contract artifact with schema ID and SHA-256.",
|
|
2496
|
+
subtask_prompt: "Materialize and validate the structured frontend contract before prewrite authorization.",
|
|
2497
|
+
shell: {
|
|
2498
|
+
commands: [],
|
|
2499
|
+
jsonArtifactGate: {
|
|
2500
|
+
fromNodeId: "frontend-contract-json-pi",
|
|
2501
|
+
schemaId: "frontend-implementation-contract-v1",
|
|
2502
|
+
artifactName: "frontend-implementation-contract.json",
|
|
2503
|
+
outputDir: "contracts",
|
|
2504
|
+
},
|
|
2505
|
+
cwd: ".",
|
|
2506
|
+
timeoutMs: 60000,
|
|
2507
|
+
},
|
|
2508
|
+
},
|
|
2458
2509
|
{
|
|
2459
2510
|
id: "frontend-prewrite-gate-shell",
|
|
2460
2511
|
depends_on: [
|
|
2512
|
+
"frontend-contract-json-pi",
|
|
2513
|
+
"frontend-contract-json-validate-shell",
|
|
2461
2514
|
"frontend-final-design-review-pi",
|
|
2462
2515
|
"frontend-design-review-pi",
|
|
2463
2516
|
"frontend-plan-revision-pi",
|
|
@@ -2476,8 +2529,8 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2476
2529
|
commands: [],
|
|
2477
2530
|
frontendPrewriteGate: {
|
|
2478
2531
|
schemaVersion: 1,
|
|
2479
|
-
planFromNodeId: "frontend-
|
|
2480
|
-
planFallbackFromNodeIds: ["frontend-plan-pi"],
|
|
2532
|
+
planFromNodeId: "frontend-contract-json-pi",
|
|
2533
|
+
planFallbackFromNodeIds: ["frontend-plan-revision-pi", "frontend-plan-pi"],
|
|
2481
2534
|
reviewFromNodeId: "frontend-final-design-review-pi",
|
|
2482
2535
|
reviewFallbackFromNodeIds: ["frontend-design-review-pi"],
|
|
2483
2536
|
requiredRequirementIds: requirementIds,
|
|
@@ -5287,7 +5340,7 @@ async function buildHybridDagForTemplate(sources, template) {
|
|
|
5287
5340
|
else if (template === "review-gated-dag")
|
|
5288
5341
|
spec = buildReviewGatedHybridDag(standard, sources);
|
|
5289
5342
|
else
|
|
5290
|
-
spec = buildSupervisedHybridDag(standard, sources);
|
|
5343
|
+
spec = await buildSupervisedHybridDag(standard, sources);
|
|
5291
5344
|
}
|
|
5292
5345
|
applyProjectGovernanceReview(spec, template, sources);
|
|
5293
5346
|
// New generate path always emits DagSpec v4 + bindings.
|
|
@@ -5721,10 +5774,12 @@ function buildWriteSetGateNode(sources) {
|
|
|
5721
5774
|
},
|
|
5722
5775
|
};
|
|
5723
5776
|
}
|
|
5724
|
-
function buildSoftVerifyNode(sources) {
|
|
5777
|
+
async function buildSoftVerifyNode(sources) {
|
|
5725
5778
|
const implementId = implementationNodeId();
|
|
5726
5779
|
const strategy = resolveDagVerifyStrategy(sources.taskConfig, "1");
|
|
5727
|
-
const fallbackCommands =
|
|
5780
|
+
const fallbackCommands = sources.repoRoot
|
|
5781
|
+
? (await discoverFrontendFallbackVerifyCommands(sources.repoRoot)).staticCommands
|
|
5782
|
+
: ["npm run typecheck"];
|
|
5728
5783
|
const focusedIntermediate = sources.verifyCommands?.intermediate.filter((command) => !isFullSuiteVerifyCommand(command));
|
|
5729
5784
|
const plannedIntermediate = applyMavenVerificationPlanning({
|
|
5730
5785
|
repoRoot: sources.repoRoot,
|
|
@@ -5966,7 +6021,7 @@ function resolveSupervisedConvergence(taskConfig) {
|
|
|
5966
6021
|
chainNodeIds: [...SUPERVISED_CONVERGENCE_CHAIN_NODE_IDS],
|
|
5967
6022
|
};
|
|
5968
6023
|
}
|
|
5969
|
-
function buildSupervisedHybridDag(standard, sources) {
|
|
6024
|
+
async function buildSupervisedHybridDag(standard, sources) {
|
|
5970
6025
|
const contract = getTaskOrThrow(standard, "contract-pi");
|
|
5971
6026
|
const scoutSrc = getTaskOrThrow(standard, "scout-src");
|
|
5972
6027
|
const scoutTests = getTaskOrThrow(standard, "scout-tests");
|
|
@@ -6018,7 +6073,7 @@ function buildSupervisedHybridDag(standard, sources) {
|
|
|
6018
6073
|
"final-write-set-audit-format-repair-pi",
|
|
6019
6074
|
],
|
|
6020
6075
|
}),
|
|
6021
|
-
buildSoftVerifyNode(sources),
|
|
6076
|
+
await buildSoftVerifyNode(sources),
|
|
6022
6077
|
buildProcessSupervisorNode(sources),
|
|
6023
6078
|
buildProcessGateNode(sources),
|
|
6024
6079
|
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": "
|
|
62
|
-
"MED": "
|
|
63
|
-
"HIGH": "
|
|
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
|
}
|