@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.
- package/CHANGELOG.md +24 -0
- package/dist/application/dag/args.js +29 -0
- package/dist/application/dag/run-dag.js +3 -1
- package/dist/cli/command-definitions.js +15 -1
- package/dist/cli/program.js +11 -1
- package/dist/commands/dag-rerun-task.js +19 -0
- package/dist/commands/dag-rerun.js +111 -0
- package/dist/shared/operator/capabilities.js +54 -0
- package/dist/worker/console/index.js +1 -1
- package/dist/worker/console/inspect-split.js +82 -0
- package/dist/worker/console/operation-runner.js +3 -1
- package/dist/worker/console/operation-store.js +1 -0
- package/dist/worker/console/operator-actions.js +153 -2
- package/dist/worker/console/operator-user-error.js +10 -0
- package/dist/worker/console/pi-readiness.js +4 -0
- package/dist/worker/console/recovery-cta.js +116 -5
- package/dist/worker/console/recovery-selection.js +107 -0
- package/dist/worker/console/resolve-dag-run-for-task.js +50 -11
- package/dist/worker/console/routes.js +20 -0
- package/dist/worker/console/sibling-controller.js +12 -7
- package/dist/worker/console/static/assets/index-CUDke82y.js +18 -0
- package/dist/worker/console/static/assets/index-wSEksVSO.css +1 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/observability/read-model.js +60 -0
- package/dist/worker/observe/static/index.html +1 -1
- package/dist/worker/run-task/run-task.js +7 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +64 -0
- package/dist/workflows/dag/init-hybrid.js +90 -48
- package/dist/workflows/dag/node-execution.js +40 -6
- package/dist/workflows/dag/output-protocol.js +76 -0
- package/dist/workflows/dag/rerun-plan.js +611 -0
- package/dist/workflows/dag/rerun-run.js +497 -0
- package/dist/workflows/dag/rerun-task.js +284 -0
- package/dist/workflows/dag/retry-policy.js +20 -1
- package/dist/workflows/dag/runner.js +50 -0
- package/dist/workflows/dag/skill-snapshot.js +22 -3
- package/dist/workflows/dag/types.js +7 -0
- package/dist/workflows/dag/validate.js +11 -0
- package/dist/workflows/dag/workspace-checkpoint.js +163 -0
- package/docs/README.md +1 -0
- package/docs/templates/agent-dag.schema.json +17 -2
- package/docs/templates/frontend-test-case-checklist.md +16 -1
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +26 -2
- package/docs/templates/frontend-test-dag.json +65 -6
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +4 -1
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +3 -0
- package/skills/playwright-cli-case-generator/SKILL.md +35 -7
- package/dist/worker/console/static/assets/index-3vsjZJHq.js +0 -16
- 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
|
-
|
|
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
|
+
}
|