@tea-agent/loop-agent 0.16.1 → 0.16.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/executors/dag-pi-executor.js +4 -2
  3. package/dist/executors/pi-sdk-executor.js +66 -3
  4. package/dist/executors/shell-executor.js +212 -29
  5. package/dist/executors/shell-presets.js +12 -2
  6. package/dist/executors/shell-write-guard.js +20 -1
  7. package/dist/shared/git-progress.js +9 -2
  8. package/dist/worker/observability/read-model.js +56 -0
  9. package/dist/worker/observe/server.js +6 -3
  10. package/dist/workflows/dag/backend-test-analysis-contract.js +87 -30
  11. package/dist/workflows/dag/backend-test-case-manifest.js +71 -8
  12. package/dist/workflows/dag/backend-test-execution-contract.js +63 -11
  13. package/dist/workflows/dag/backend-test-repair-contract.js +94 -0
  14. package/dist/workflows/dag/backend-test-result-contract.js +6 -4
  15. package/dist/workflows/dag/backend-test-semantic-review-contract.js +36 -0
  16. package/dist/workflows/dag/dynamic-runtime/condition.js +1 -1
  17. package/dist/workflows/dag/dynamic-runtime/shared.js +42 -0
  18. package/dist/workflows/dag/failure-routing.js +1 -1
  19. package/dist/workflows/dag/frontend-implementation-contract.js +32 -16
  20. package/dist/workflows/dag/init-hybrid.js +591 -119
  21. package/dist/workflows/dag/lifecycle.js +33 -2
  22. package/dist/workflows/dag/scheduler.js +87 -17
  23. package/dist/workflows/dag/types.js +31 -0
  24. package/dist/workflows/dag/validate.js +20 -14
  25. package/docs/templates/agent-dag.schema.json +25 -2
  26. package/docs/templates/backend-test-analysis.schema.json +9 -16
  27. package/docs/templates/backend-test-dag.json +493 -197
  28. package/docs/templates/backend-test-dag.review-cases.prompt.md +10 -4
  29. package/docs/templates/backend-test-execution.schema.json +6 -1
  30. package/package.json +1 -1
  31. package/skills/loop-agent/references/hybrid-dag.md +4 -1
@@ -186,6 +186,26 @@ export function extractFrontendImplementationJson(text) {
186
186
  throw new Error("output must contain exactly one fenced json object");
187
187
  return JSON.parse(blocks[0][1]);
188
188
  }
189
+ /**
190
+ * Build the authoritative frontend-implementation-contract sourceBinding from
191
+ * the DAG-owned binding. Model JSON must not invent taskId/path/sha256; the
192
+ * gate overwrites whatever the model emitted so identity cannot drift.
193
+ */
194
+ export function canonicalFrontendContractSourceBinding(binding) {
195
+ const requirement = binding.sources.find((source) => source.kind === "requirement");
196
+ if (!requirement)
197
+ throw new Error("frontend implementation contract gate requires a requirement source in DAG sourceBinding");
198
+ return {
199
+ taskId: binding.taskId,
200
+ requirementPath: requirement.path,
201
+ requirementSha256: requirement.sha256,
202
+ referencePaths: binding.sources
203
+ .filter((source) => source.kind === "reference")
204
+ .map((source) => source.path)
205
+ .sort(),
206
+ requirementIds: [...binding.requirementIds],
207
+ };
208
+ }
189
209
  export async function materializeFrontendImplementationContract(input) {
190
210
  if (!input.sourceBinding)
191
211
  throw new Error("frontend implementation contract gate requires DAG sourceBinding");
@@ -200,23 +220,19 @@ export async function materializeFrontendImplementationContract(input) {
200
220
  const secrets = secretIssues(parsed);
201
221
  if (secrets.length)
202
222
  throw new Error(`invalid-output: ${secrets.join("; ")}`);
203
- const result = frontendImplementationContractSchema.safeParse(parsed);
223
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
224
+ throw new Error("invalid-output: frontend contract must be a JSON object");
225
+ const canonicalBinding = canonicalFrontendContractSourceBinding(input.sourceBinding);
226
+ // Always inject DAG-owned identity. Model-provided sourceBinding is advisory
227
+ // only and must not fail a otherwise-valid contract (common live failure:
228
+ // wrong requirementPath/sha, extra referencePaths, or omitted binding).
229
+ const withCanonicalBinding = {
230
+ ...parsed,
231
+ sourceBinding: canonicalBinding,
232
+ };
233
+ const result = frontendImplementationContractSchema.safeParse(withCanonicalBinding);
204
234
  if (!result.success)
205
235
  throw new Error(`invalid-output: ${result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")}`);
206
- const requirement = input.sourceBinding.sources.find((source) => source.kind === "requirement");
207
- const refs = input.sourceBinding.sources
208
- .filter((source) => source.kind === "reference")
209
- .map((source) => source.path)
210
- .sort();
211
- const bound = result.data.sourceBinding;
212
- if (!requirement ||
213
- bound.taskId !== input.sourceBinding.taskId ||
214
- bound.requirementPath !== requirement.path ||
215
- bound.requirementSha256 !== requirement.sha256 ||
216
- JSON.stringify([...bound.referencePaths].sort()) !== JSON.stringify(refs) ||
217
- JSON.stringify(bound.requirementIds) !==
218
- JSON.stringify(input.sourceBinding.requirementIds))
219
- throw new Error("frontend contract source binding does not match DAG source binding");
220
236
  const blockingGaps = [
221
237
  ...result.data.evidenceGaps,
222
238
  ...result.data.requirements.flatMap((item) => item.evidenceGap ? [item.evidenceGap] : []),
@@ -225,7 +241,7 @@ export async function materializeFrontendImplementationContract(input) {
225
241
  throw new Error(`frontend contract has blocking evidence gap: ${blockingGaps
226
242
  .map((item) => item.requirementId ?? item.description)
227
243
  .join(", ")}`);
228
- for (const requirementId of bound.requirementIds)
244
+ for (const requirementId of canonicalBinding.requirementIds)
229
245
  if (!result.data.requirements.some((item) => item.id === requirementId) &&
230
246
  !result.data.evidenceGaps.some((item) => item.requirementId === requirementId))
231
247
  throw new Error(`frontend contract does not cover ${requirementId}`);