@tea-agent/loop-agent 0.20.1-beta.0 → 0.20.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/application/dag/args.js +29 -0
  3. package/dist/application/dag/run-dag.js +3 -1
  4. package/dist/cli/command-definitions.js +15 -1
  5. package/dist/cli/program.js +11 -1
  6. package/dist/commands/dag-rerun-task.js +19 -0
  7. package/dist/commands/dag-rerun.js +111 -0
  8. package/dist/shared/operator/capabilities.js +54 -0
  9. package/dist/worker/console/index.js +1 -1
  10. package/dist/worker/console/inspect-split.js +82 -0
  11. package/dist/worker/console/operation-runner.js +3 -1
  12. package/dist/worker/console/operation-store.js +1 -0
  13. package/dist/worker/console/operator-actions.js +153 -2
  14. package/dist/worker/console/operator-user-error.js +10 -0
  15. package/dist/worker/console/pi-readiness.js +4 -0
  16. package/dist/worker/console/recovery-cta.js +116 -5
  17. package/dist/worker/console/recovery-selection.js +107 -0
  18. package/dist/worker/console/resolve-dag-run-for-task.js +50 -11
  19. package/dist/worker/console/routes.js +20 -0
  20. package/dist/worker/console/sibling-controller.js +12 -7
  21. package/dist/worker/console/static/assets/index-CUDke82y.js +18 -0
  22. package/dist/worker/console/static/assets/index-wSEksVSO.css +1 -0
  23. package/dist/worker/console/static/index.html +2 -2
  24. package/dist/worker/observability/read-model.js +60 -0
  25. package/dist/worker/observe/static/index.html +1 -1
  26. package/dist/worker/run-task/run-task.js +7 -0
  27. package/dist/workflows/dag/frontend-test-result-contract.js +64 -0
  28. package/dist/workflows/dag/init-hybrid.js +90 -48
  29. package/dist/workflows/dag/node-execution.js +40 -6
  30. package/dist/workflows/dag/output-protocol.js +76 -0
  31. package/dist/workflows/dag/rerun-plan.js +611 -0
  32. package/dist/workflows/dag/rerun-run.js +497 -0
  33. package/dist/workflows/dag/rerun-task.js +284 -0
  34. package/dist/workflows/dag/retry-policy.js +20 -1
  35. package/dist/workflows/dag/runner.js +50 -0
  36. package/dist/workflows/dag/skill-snapshot.js +22 -3
  37. package/dist/workflows/dag/types.js +7 -0
  38. package/dist/workflows/dag/validate.js +11 -0
  39. package/dist/workflows/dag/workspace-checkpoint.js +163 -0
  40. package/docs/README.md +1 -0
  41. package/docs/templates/agent-dag.schema.json +17 -2
  42. package/docs/templates/frontend-test-case-checklist.md +16 -1
  43. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +26 -2
  44. package/docs/templates/frontend-test-dag.json +65 -6
  45. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +4 -1
  46. package/package.json +1 -1
  47. package/skills/loop-agent/references/command-reference.md +3 -0
  48. package/skills/playwright-cli-case-generator/SKILL.md +35 -7
  49. package/dist/worker/console/static/assets/index-3vsjZJHq.js +0 -16
  50. package/dist/worker/console/static/assets/index-i1wV4LrY.css +0 -1
@@ -8,6 +8,7 @@ import { resolveContextPolicy } from "./context-policy.js";
8
8
  import { buildDagNodePromptEnvelope } from "./prompt.js";
9
9
  import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
10
10
  import { computeBackoffDelayMs, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, } from "./retry-policy.js";
11
+ import { buildProtocolRetryInstruction, validateOutputProtocol, } from "./output-protocol.js";
11
12
  import { writeDagNodeJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
12
13
  import { buildProjectGovernanceContext, readCompletedWriterChangeManifests, writeProjectGovernanceContext, } from "./project-governance-context.js";
13
14
  import { assertSkillSnapshotCoversSpec, buildNodePromptFromSnapshot, isDagSkillSnapshotIntegrityError, readSkillSnapshot, } from "./skill-snapshot.js";
@@ -25,9 +26,19 @@ export function buildNodePrompt(spec, task, upstream, options) {
25
26
  projectGovernanceContext: options?.projectGovernanceContext,
26
27
  });
27
28
  }
28
- function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory) {
29
- if (attemptNumber <= 1 ||
30
- task.outputMode !== "structured-required" ||
29
+ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason) {
30
+ if (attemptNumber <= 1)
31
+ return basePrompt;
32
+ if (previousFailureCategory === "protocol-invalid" &&
33
+ task.outputProtocol &&
34
+ previousProtocolReason) {
35
+ return [
36
+ basePrompt,
37
+ "",
38
+ buildProtocolRetryInstruction(task.outputProtocol, previousProtocolReason),
39
+ ].join("\n");
40
+ }
41
+ if (task.outputMode !== "structured-required" ||
31
42
  previousFailureCategory !== "output-too-large") {
32
43
  return basePrompt;
33
44
  }
@@ -301,6 +312,7 @@ export async function executeDagNode(input) {
301
312
  let totalBackoffMs = 0;
302
313
  let terminalResult;
303
314
  let previousFailureCategory;
315
+ let previousProtocolReason;
304
316
  for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
305
317
  const attemptStartedAt = new Date().toISOString();
306
318
  const attemptStarted = Date.now();
@@ -315,7 +327,7 @@ export async function executeDagNode(input) {
315
327
  task,
316
328
  cwd,
317
329
  model,
318
- prompt: buildAttemptPrompt(task, prompt, attemptNumber, previousFailureCategory),
330
+ prompt: buildAttemptPrompt(task, prompt, attemptNumber, previousFailureCategory, previousProtocolReason),
319
331
  });
320
332
  }
321
333
  catch (error) {
@@ -326,6 +338,26 @@ export async function executeDagNode(input) {
326
338
  durationMs: Date.now() - attemptStarted,
327
339
  };
328
340
  }
341
+ // R0: executor ok=true still fails closed when outputProtocol is violated.
342
+ // Valid semantic results (e.g. VERDICT: request-revision) pass validation.
343
+ if (result.ok && task.outputProtocol) {
344
+ const protocolText = `${result.assistantText ?? ""}\n${result.stdout ?? ""}`;
345
+ const protocolCheck = validateOutputProtocol(task.outputProtocol, protocolText);
346
+ if (!protocolCheck.ok) {
347
+ result = {
348
+ ...result,
349
+ ok: false,
350
+ failureCategory: protocolCheck.failureCategory,
351
+ stderr: [result.stderr, protocolCheck.reason]
352
+ .filter(Boolean)
353
+ .join("\n"),
354
+ };
355
+ previousProtocolReason = protocolCheck.reason;
356
+ }
357
+ else {
358
+ previousProtocolReason = undefined;
359
+ }
360
+ }
329
361
  const attemptFinishedAt = new Date().toISOString();
330
362
  const attemptRecord = {
331
363
  attempt: attemptNumber,
@@ -384,9 +416,11 @@ export async function executeDagNode(input) {
384
416
  break;
385
417
  const canRetry = retryPolicy !== undefined && attemptNumber < maxAttempts;
386
418
  const retryable = retryPolicy !== undefined &&
387
- isRetryablePiFailureCategory(result.failureCategory, {
419
+ (isRetryablePiFailureCategory(result.failureCategory, {
388
420
  retryCategories: retryPolicy.retryCategories,
389
- });
421
+ }) ||
422
+ (result.failureCategory === "protocol-invalid" &&
423
+ task.outputProtocol?.retryOnInvalid === true));
390
424
  if (!canRetry || !retryable)
391
425
  break;
392
426
  const delayMs = computeBackoffDelayMs(attemptNumber + 1, retryPolicy);
@@ -0,0 +1,76 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Machine-readable output protocol for safe read-only Pi nodes.
4
+ *
5
+ * Phase 1 only supports first-line-enum (e.g. VERDICT lines). Structured JSON
6
+ * continues to use existing structured-required / deterministic gates.
7
+ */
8
+ export const dagOutputProtocolSchema = z
9
+ .object({
10
+ type: z.literal("first-line-enum"),
11
+ validLines: z.array(z.string().min(1)).min(1),
12
+ retryOnInvalid: z.boolean().default(true),
13
+ })
14
+ .strict();
15
+ /** Reviewer VERDICT protocol used by reviewed/supervised DAGs. */
16
+ export const REVIEW_VERDICT_OUTPUT_PROTOCOL = {
17
+ type: "first-line-enum",
18
+ validLines: ["VERDICT: pass", "VERDICT: request-revision"],
19
+ retryOnInvalid: true,
20
+ };
21
+ /**
22
+ * Extract the first non-empty line from assistant/stdout text.
23
+ */
24
+ export function firstNonEmptyLine(text) {
25
+ for (const line of text.split("\n")) {
26
+ const trimmed = line.trim();
27
+ if (trimmed)
28
+ return trimmed;
29
+ }
30
+ return undefined;
31
+ }
32
+ /**
33
+ * Validate node output against an explicit outputProtocol.
34
+ * Pure function — does not mutate run facts.
35
+ */
36
+ export function validateOutputProtocol(protocol, text) {
37
+ if (protocol.type !== "first-line-enum") {
38
+ return {
39
+ ok: false,
40
+ failureCategory: "protocol-invalid",
41
+ reason: `unsupported outputProtocol.type: ${protocol.type ?? "unknown"}`,
42
+ };
43
+ }
44
+ const first = firstNonEmptyLine(text);
45
+ if (!first) {
46
+ return {
47
+ ok: false,
48
+ failureCategory: "protocol-invalid",
49
+ reason: `missing first non-empty line; expected one of: ${protocol.validLines.map((l) => JSON.stringify(l)).join(" or ")}`,
50
+ };
51
+ }
52
+ if (!protocol.validLines.includes(first)) {
53
+ return {
54
+ ok: false,
55
+ failureCategory: "protocol-invalid",
56
+ reason: `first non-empty line ${JSON.stringify(first)} is not a valid protocol line; expected one of: ${protocol.validLines.map((l) => JSON.stringify(l)).join(" or ")}`,
57
+ firstNonEmptyLine: first,
58
+ };
59
+ }
60
+ return { ok: true, matchedLine: first };
61
+ }
62
+ /**
63
+ * Correction instruction appended on protocol-invalid retry attempts.
64
+ */
65
+ export function buildProtocolRetryInstruction(protocol, reason) {
66
+ const expected = protocol.validLines
67
+ .map((line) => JSON.stringify(line))
68
+ .join(" or ");
69
+ return [
70
+ "<retry_instruction>",
71
+ "Previous attempt violated the output protocol:",
72
+ reason,
73
+ `Return one valid protocol line before any explanation. Expected first non-empty line: ${expected}.`,
74
+ "</retry_instruction>",
75
+ ].join("\n");
76
+ }