@tea-agent/loop-agent 0.34.0 → 0.34.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 +30 -0
- package/dist/application/task-lifecycle/advance.js +57 -7
- package/dist/application/task-lifecycle/plan-transitions.js +13 -21
- package/dist/executors/dag-pi-executor.js +18 -3
- package/dist/shared/operator/capabilities.js +26 -2
- package/dist/task/source-prepare/prepare.js +49 -2
- package/dist/worker/console/interview/grill-me.js +7 -6
- package/dist/worker/console/operator-actions.js +231 -22
- package/dist/worker/console/prd-intake-bridge.js +393 -0
- package/dist/worker/console/recovery-cta.js +35 -6
- package/dist/worker/console/static/assets/index-BpuHmlSP.js +29 -0
- package/dist/worker/console/static/index.html +1 -1
- package/dist/worker/console/static-src/app/console-types.js +1 -0
- package/dist/worker/console/static-src/app/usePrdImport.js +2 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +24 -1
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +19 -1
- package/dist/worker/console/static-src/app/useTaskWizard.js +57 -2
- package/dist/worker/observability/read-model.js +3 -0
- package/dist/workflows/dag/failure-category.js +3 -0
- package/dist/workflows/dag/init-hybrid.js +15 -9
- package/dist/workflows/dag/node-execution.js +3 -2
- package/dist/workflows/dag/retry-policy.js +44 -11
- package/dist/workflows/dag/runner.js +6 -0
- package/dist/workflows/dag/scheduler.js +49 -1
- package/dist/workflows/dag/validate.js +16 -2
- package/docs/templates/agent-dag.schema.json +4 -4
- package/package.json +1 -1
- package/dist/worker/console/static/assets/index-CMHovlqG.js +0 -32
|
@@ -16,6 +16,7 @@ import { applyGrillMeAnswer, draftSummary, emptyDraft, isDraftStructurallyComple
|
|
|
16
16
|
import { toCanonicalDraftForCli, } from "./draft-store.js";
|
|
17
17
|
import { normalizeConsoleWorkflowKind } from "./workflow-kinds.js";
|
|
18
18
|
import { deriveTaskIdentityFromPrd, nextTaskIdRevision, } from "./prd-identity.js";
|
|
19
|
+
import { appendEngineeringCliArgs, buildDraftFromTaskIntake, parseEngineeringBoundaryFromParams, } from "./prd-intake-bridge.js";
|
|
19
20
|
import { resolveSiblingAgentWorkerBin } from "./sibling-controller.js";
|
|
20
21
|
import { projectOperationEventSummary, projectOperationForChat, } from "./chat/chat-event-store.js";
|
|
21
22
|
import { MutationGateReceiptStore } from "./mutation-gate-receipt-store.js";
|
|
@@ -23,8 +24,9 @@ import { assessAutonomousExecutionEligibility, deriveEligibilityFactsFromDagSpec
|
|
|
23
24
|
import { issueHumanGateToken, mutationGatePayloadHash, verifyHumanGateToken, } from "./human-gate-token.js";
|
|
24
25
|
const MUTATION_GATE_ACTIONS = new Set([
|
|
25
26
|
// 2026-08-11: standaloneTaskRerun / workerTaskRetry are autonomous (server
|
|
26
|
-
// run-facts checks)
|
|
27
|
-
// Human Gate.
|
|
27
|
+
// run-facts checks). Destructive DAG reconciliation and Night Scheduler
|
|
28
|
+
// mutations still need the browser Human Gate.
|
|
29
|
+
"dagReconcileRun",
|
|
28
30
|
"workerAdmissionPrepare",
|
|
29
31
|
"workerSchedulerAdd",
|
|
30
32
|
"workerSchedulerCancel",
|
|
@@ -40,6 +42,7 @@ const MUTATION_OR_LONG = new Set([
|
|
|
40
42
|
"dagRunTask",
|
|
41
43
|
"runDag",
|
|
42
44
|
"dagRerun",
|
|
45
|
+
"dagReconcileRun",
|
|
43
46
|
"standaloneTaskRerun",
|
|
44
47
|
"workerTaskRetry",
|
|
45
48
|
]);
|
|
@@ -1354,23 +1357,106 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1354
1357
|
case "bootstrapFromPrd":
|
|
1355
1358
|
return dispatchBootstrapFromPrd(ctx, p);
|
|
1356
1359
|
case "importPrd": {
|
|
1360
|
+
// Chat / operator path: same intake bridge as bootstrapFromPrd, but
|
|
1361
|
+
// for an existing taskId (create-if-missing via advance --prd).
|
|
1357
1362
|
const taskId = str(p.taskId);
|
|
1358
1363
|
const content = str(p.content) ?? str(p.fileText);
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
return invalid(action, "taskId, content, and clientRequestId are required");
|
|
1364
|
+
if (!taskId || !content) {
|
|
1365
|
+
return invalid(action, "taskId and content are required");
|
|
1362
1366
|
}
|
|
1367
|
+
const taskKind = normalizeConsoleWorkflowKind(str(p.taskKind), "standard");
|
|
1368
|
+
const engineering = parseEngineeringBoundaryFromParams(p);
|
|
1369
|
+
const identity = deriveTaskIdentityFromPrd(content);
|
|
1370
|
+
const title = str(p.title) ?? identity.title;
|
|
1371
|
+
const run = ctx.runCommand ?? ((a, o) => ctx.client.run(a, o));
|
|
1363
1372
|
const staged = await stageUtf8Text(ctx.appData, content, {
|
|
1364
1373
|
extension: ".md",
|
|
1365
1374
|
});
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1375
|
+
const imported = await run([
|
|
1376
|
+
"task",
|
|
1377
|
+
"advance",
|
|
1378
|
+
taskId,
|
|
1379
|
+
title,
|
|
1380
|
+
"--prd",
|
|
1381
|
+
staged.path,
|
|
1382
|
+
"--role",
|
|
1383
|
+
"requirement",
|
|
1384
|
+
"--json",
|
|
1385
|
+
], {
|
|
1386
|
+
cwd: ctx.repoRoot,
|
|
1387
|
+
artifactName: "import-prd-task-advance",
|
|
1388
|
+
expectJson: true,
|
|
1389
|
+
timeoutMs: 60_000,
|
|
1390
|
+
});
|
|
1391
|
+
if (!imported.ok || imported.exitCode !== 0 || imported.timedOut) {
|
|
1392
|
+
return {
|
|
1393
|
+
kind: "error",
|
|
1394
|
+
status: 400,
|
|
1395
|
+
body: operatorFailed({
|
|
1396
|
+
command: action,
|
|
1397
|
+
outcome: "rejected",
|
|
1398
|
+
code: "INTERNAL_ERROR",
|
|
1399
|
+
message: imported.stderr?.trim() ||
|
|
1400
|
+
`task advance --prd failed with exit ${imported.exitCode}`,
|
|
1401
|
+
details: {
|
|
1402
|
+
exitCode: imported.exitCode,
|
|
1403
|
+
timedOut: imported.timedOut,
|
|
1404
|
+
taskId,
|
|
1405
|
+
},
|
|
1406
|
+
}),
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
const intakeArgs = ["task", "advance", taskId, "--json"];
|
|
1410
|
+
if (taskKind && taskKind !== "standard") {
|
|
1411
|
+
intakeArgs.push("--task-kind", taskKind);
|
|
1412
|
+
}
|
|
1413
|
+
appendEngineeringCliArgs(intakeArgs, engineering);
|
|
1414
|
+
const intake = await run(intakeArgs, {
|
|
1415
|
+
cwd: ctx.repoRoot,
|
|
1416
|
+
artifactName: "import-prd-task-advance-intake",
|
|
1417
|
+
expectJson: true,
|
|
1418
|
+
timeoutMs: 300_000,
|
|
1419
|
+
});
|
|
1420
|
+
const intakeJson = intake.json ?? null;
|
|
1421
|
+
const intakeOk = intake.ok && intake.exitCode === 0 && !intake.timedOut;
|
|
1422
|
+
const bridged = await buildDraftFromTaskIntake({
|
|
1423
|
+
repoRoot: ctx.repoRoot,
|
|
1370
1424
|
taskId,
|
|
1371
|
-
|
|
1372
|
-
|
|
1425
|
+
title,
|
|
1426
|
+
taskKind,
|
|
1427
|
+
advanceJson: intakeJson,
|
|
1428
|
+
engineering,
|
|
1373
1429
|
});
|
|
1430
|
+
const draftSaved = await ctx.drafts.save(bridged.draft);
|
|
1431
|
+
const parseStatus = intakeOk || bridged.parseStatus !== "pending"
|
|
1432
|
+
? bridged.parseStatus
|
|
1433
|
+
: "failed";
|
|
1434
|
+
return {
|
|
1435
|
+
kind: "sync",
|
|
1436
|
+
status: 200,
|
|
1437
|
+
body: operatorSucceeded(action, {
|
|
1438
|
+
taskId,
|
|
1439
|
+
title: draftSaved.draft.title ?? title,
|
|
1440
|
+
taskKind,
|
|
1441
|
+
draftSha256: draftSaved.draftSha256,
|
|
1442
|
+
parseStatus,
|
|
1443
|
+
semanticIntake: bridged.semanticIntake,
|
|
1444
|
+
gaps: bridged.gaps,
|
|
1445
|
+
lifecycleState: bridged.lifecycleState ?? "intake-incomplete",
|
|
1446
|
+
intake: {
|
|
1447
|
+
ok: intakeOk,
|
|
1448
|
+
exitCode: intake.exitCode,
|
|
1449
|
+
timedOut: intake.timedOut,
|
|
1450
|
+
},
|
|
1451
|
+
import: imported.json ?? null,
|
|
1452
|
+
draftPreview: {
|
|
1453
|
+
objective: draftSaved.draft.requirement?.objective ?? null,
|
|
1454
|
+
scope: draftSaved.draft.requirement?.scope ?? [],
|
|
1455
|
+
acceptanceCriteria: draftSaved.draft.requirement?.acceptanceCriteria ?? [],
|
|
1456
|
+
allowedPaths: draftSaved.draft.constraints?.allowedPaths ?? [],
|
|
1457
|
+
},
|
|
1458
|
+
}),
|
|
1459
|
+
};
|
|
1374
1460
|
}
|
|
1375
1461
|
case "contractApply": {
|
|
1376
1462
|
const taskId = str(p.taskId);
|
|
@@ -1751,6 +1837,49 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1751
1837
|
],
|
|
1752
1838
|
});
|
|
1753
1839
|
}
|
|
1840
|
+
case "dagReconcileRun": {
|
|
1841
|
+
// S6 Recovery: orphan/interrupted runs — structured reason is the human
|
|
1842
|
+
// confirmation surface (CLI eligibility still fail-closed).
|
|
1843
|
+
const runId = str(p.runId);
|
|
1844
|
+
const reason = str(p.reason);
|
|
1845
|
+
const clientRequestId = str(req.clientRequestId);
|
|
1846
|
+
const reconcileActionRaw = str(p.reconcileAction) ?? "supersede";
|
|
1847
|
+
if (reconcileActionRaw !== "supersede" &&
|
|
1848
|
+
reconcileActionRaw !== "abandon") {
|
|
1849
|
+
return invalid(action, "reconcileAction must be supersede or abandon");
|
|
1850
|
+
}
|
|
1851
|
+
const reconcileAction = reconcileActionRaw;
|
|
1852
|
+
if (!runId || !reason || !clientRequestId) {
|
|
1853
|
+
return invalid(action, "runId, reason, and clientRequestId are required");
|
|
1854
|
+
}
|
|
1855
|
+
const gated = await consumeMutationGateReceipt(ctx, action, p);
|
|
1856
|
+
if (gated.kind === "error")
|
|
1857
|
+
return gated;
|
|
1858
|
+
if (gated.kind === "idempotent")
|
|
1859
|
+
return gated.result;
|
|
1860
|
+
const accepted = await acceptOperation(ctx, {
|
|
1861
|
+
action,
|
|
1862
|
+
actionParams: {
|
|
1863
|
+
runId,
|
|
1864
|
+
reason,
|
|
1865
|
+
reconcileAction,
|
|
1866
|
+
},
|
|
1867
|
+
clientRequestId,
|
|
1868
|
+
cliArgs: [
|
|
1869
|
+
"dag",
|
|
1870
|
+
"reconcile-run",
|
|
1871
|
+
"--run-id",
|
|
1872
|
+
runId,
|
|
1873
|
+
"--action",
|
|
1874
|
+
reconcileAction,
|
|
1875
|
+
"--reason",
|
|
1876
|
+
reason,
|
|
1877
|
+
"--json",
|
|
1878
|
+
],
|
|
1879
|
+
});
|
|
1880
|
+
await finalizeMutationGateReceipt(ctx, gated.receiptId, accepted);
|
|
1881
|
+
return accepted;
|
|
1882
|
+
}
|
|
1754
1883
|
case "standaloneTaskRerun": {
|
|
1755
1884
|
const runId = str(p.runId);
|
|
1756
1885
|
const reason = str(p.reason);
|
|
@@ -2001,7 +2130,7 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
2001
2130
|
return invalid(action, "action and actionParams are required");
|
|
2002
2131
|
}
|
|
2003
2132
|
if (!MUTATION_GATE_ACTIONS.has(targetAction)) {
|
|
2004
|
-
return invalid(action, "action must be a mutation-gate target (workerAdmissionPrepare | workerSchedulerAdd | workerSchedulerCancel | workerSchedulerHarvest | workerSchedulerDiscard)");
|
|
2133
|
+
return invalid(action, "action must be a mutation-gate target (dagReconcileRun | workerAdmissionPrepare | workerSchedulerAdd | workerSchedulerCancel | workerSchedulerHarvest | workerSchedulerDiscard)");
|
|
2005
2134
|
}
|
|
2006
2135
|
const secret = ctx.humanGateSecret;
|
|
2007
2136
|
if (!secret) {
|
|
@@ -2367,6 +2496,7 @@ async function dispatchBootstrapFromPrd(ctx, p) {
|
|
|
2367
2496
|
}
|
|
2368
2497
|
const documents = parsedDocs.documents;
|
|
2369
2498
|
const taskKind = normalizeConsoleWorkflowKind(str(p.taskKind), "standard");
|
|
2499
|
+
const engineering = parseEngineeringBoundaryFromParams(p);
|
|
2370
2500
|
const identity = deriveTaskIdentityFromPrd(content);
|
|
2371
2501
|
let taskId;
|
|
2372
2502
|
try {
|
|
@@ -2495,27 +2625,66 @@ async function dispatchBootstrapFromPrd(ctx, p) {
|
|
|
2495
2625
|
}
|
|
2496
2626
|
importResults.push(imported.json ?? null);
|
|
2497
2627
|
}
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2628
|
+
// Phase 2 (design 2026-08-11): drive task advance intake after import so
|
|
2629
|
+
// deterministic parse + one-shot Semantic Intake run on archived references.
|
|
2630
|
+
// Timeout is longer than import: Pi MED may take minutes.
|
|
2631
|
+
const intakeArgs = ["task", "advance", taskId, "--json"];
|
|
2632
|
+
if (taskKind && taskKind !== "standard") {
|
|
2633
|
+
intakeArgs.push("--task-kind", taskKind);
|
|
2634
|
+
}
|
|
2635
|
+
appendEngineeringCliArgs(intakeArgs, engineering);
|
|
2636
|
+
const intake = await run(intakeArgs, {
|
|
2637
|
+
cwd: ctx.repoRoot,
|
|
2638
|
+
artifactName: "bootstrap-task-advance-intake",
|
|
2639
|
+
expectJson: true,
|
|
2640
|
+
timeoutMs: 300_000,
|
|
2641
|
+
});
|
|
2642
|
+
const intakeJson = intake.json ?? null;
|
|
2643
|
+
const intakeOk = intake.ok && intake.exitCode === 0 && !intake.timedOut;
|
|
2644
|
+
const bridged = await buildDraftFromTaskIntake({
|
|
2645
|
+
repoRoot: ctx.repoRoot,
|
|
2646
|
+
taskId,
|
|
2647
|
+
title,
|
|
2648
|
+
taskKind,
|
|
2649
|
+
advanceJson: intakeJson,
|
|
2650
|
+
engineering,
|
|
2503
2651
|
});
|
|
2652
|
+
// Never fall back to grill-me product templates when intake fails.
|
|
2653
|
+
const draftSaved = await ctx.drafts.save(bridged.draft);
|
|
2654
|
+
const parseStatus = intakeOk || bridged.parseStatus !== "pending"
|
|
2655
|
+
? bridged.parseStatus
|
|
2656
|
+
: "failed";
|
|
2504
2657
|
return {
|
|
2505
2658
|
kind: "sync",
|
|
2506
2659
|
status: 200,
|
|
2507
2660
|
body: operatorSucceeded("bootstrapFromPrd", {
|
|
2508
2661
|
taskId,
|
|
2509
|
-
title,
|
|
2662
|
+
title: draftSaved.draft.title ?? title,
|
|
2510
2663
|
taskKind,
|
|
2511
2664
|
identitySource: identity.source,
|
|
2512
2665
|
preferredTaskId: identity.taskId,
|
|
2513
2666
|
draftSha256: draftSaved.draftSha256,
|
|
2667
|
+
parseStatus,
|
|
2668
|
+
semanticIntake: bridged.semanticIntake,
|
|
2669
|
+
gaps: bridged.gaps,
|
|
2670
|
+
lifecycleState: bridged.lifecycleState ??
|
|
2671
|
+
(intakeOk ? "intake-incomplete" : "intake-incomplete"),
|
|
2672
|
+
intake: {
|
|
2673
|
+
ok: intakeOk,
|
|
2674
|
+
exitCode: intake.exitCode,
|
|
2675
|
+
timedOut: intake.timedOut,
|
|
2676
|
+
json: intakeJson,
|
|
2677
|
+
},
|
|
2514
2678
|
import: importResults.length === 1 ? importResults[0] : null,
|
|
2515
2679
|
imports: importResults,
|
|
2516
2680
|
documentNames: documents && documents.length > 0
|
|
2517
2681
|
? [...documents.map((d) => d.name), "composed"]
|
|
2518
2682
|
: undefined,
|
|
2683
|
+
draftPreview: {
|
|
2684
|
+
objective: draftSaved.draft.requirement?.objective ?? null,
|
|
2685
|
+
scope: draftSaved.draft.requirement?.scope ?? [],
|
|
2686
|
+
acceptanceCriteria: draftSaved.draft.requirement?.acceptanceCriteria ?? [],
|
|
2687
|
+
},
|
|
2519
2688
|
}),
|
|
2520
2689
|
};
|
|
2521
2690
|
}
|
|
@@ -2697,8 +2866,9 @@ async function dispatchInterviewStart(ctx, p) {
|
|
|
2697
2866
|
};
|
|
2698
2867
|
}
|
|
2699
2868
|
/**
|
|
2700
|
-
* Skip interactive grilling
|
|
2701
|
-
*
|
|
2869
|
+
* Skip interactive grilling only when requirement structure is already present
|
|
2870
|
+
* (from PRD intake) and remaining gaps are engineering/taskKind that can take
|
|
2871
|
+
* non-template recommendations. Never invent console product templates.
|
|
2702
2872
|
*/
|
|
2703
2873
|
async function dispatchInterviewSkip(ctx, p) {
|
|
2704
2874
|
const taskId = str(p.taskId);
|
|
@@ -2707,11 +2877,50 @@ async function dispatchInterviewSkip(ctx, p) {
|
|
|
2707
2877
|
const title = str(p.title) ?? taskId;
|
|
2708
2878
|
const taskKind = str(p.taskKind);
|
|
2709
2879
|
let { draft } = await ensureDraft(ctx, taskId, title, { taskKind });
|
|
2880
|
+
const requirementGaps = new Set([
|
|
2881
|
+
"objective",
|
|
2882
|
+
"scope",
|
|
2883
|
+
"nonGoals",
|
|
2884
|
+
"acceptanceCriteria",
|
|
2885
|
+
]);
|
|
2886
|
+
const currentGaps = draftSummary(draft).gaps;
|
|
2887
|
+
const missingRequirement = currentGaps.filter((g) => requirementGaps.has(g));
|
|
2888
|
+
if (missingRequirement.length > 0) {
|
|
2889
|
+
return {
|
|
2890
|
+
kind: "error",
|
|
2891
|
+
status: 400,
|
|
2892
|
+
body: operatorFailed({
|
|
2893
|
+
command: "interviewSkip",
|
|
2894
|
+
outcome: "invalid",
|
|
2895
|
+
code: "REQUIREMENT_INCOMPLETE",
|
|
2896
|
+
message: "无法跳过:需求层尚未结构化(缺 objective/scope/AC 等)。请先导入 PRD 完成 intake,或手动访谈补齐,禁止用模板冒充解析结果。",
|
|
2897
|
+
details: {
|
|
2898
|
+
gaps: currentGaps,
|
|
2899
|
+
missingRequirement,
|
|
2900
|
+
},
|
|
2901
|
+
}),
|
|
2902
|
+
};
|
|
2903
|
+
}
|
|
2710
2904
|
const accepted = [];
|
|
2905
|
+
// Only auto-accept engineering / taskKind recommendations; never product templates.
|
|
2906
|
+
const autoAcceptGaps = new Set([
|
|
2907
|
+
"taskKind",
|
|
2908
|
+
"allowedPaths",
|
|
2909
|
+
"forbiddenPaths",
|
|
2910
|
+
"verifyCommands",
|
|
2911
|
+
]);
|
|
2711
2912
|
for (let i = 0; i < 24; i += 1) {
|
|
2712
2913
|
const question = nextGrillMeQuestion(draft);
|
|
2713
2914
|
if (!question)
|
|
2714
2915
|
break;
|
|
2916
|
+
if (!autoAcceptGaps.has(question.gapId))
|
|
2917
|
+
break;
|
|
2918
|
+
// Refuse placeholder recommendations that are not real path/command values.
|
|
2919
|
+
if (question.gapId === "allowedPaths" &&
|
|
2920
|
+
(/请|示例|模板|(/.test(question.recommendation) ||
|
|
2921
|
+
!question.recommendation.includes("*"))) {
|
|
2922
|
+
break;
|
|
2923
|
+
}
|
|
2715
2924
|
draft = applyGrillMeAnswer(draft, question, {
|
|
2716
2925
|
response: "accept-recommendation",
|
|
2717
2926
|
});
|
|
@@ -2728,8 +2937,8 @@ async function dispatchInterviewSkip(ctx, p) {
|
|
|
2728
2937
|
body: operatorFailed({
|
|
2729
2938
|
command: "interviewSkip",
|
|
2730
2939
|
outcome: "invalid",
|
|
2731
|
-
code: "
|
|
2732
|
-
message: "
|
|
2940
|
+
code: "ENGINEERING_BOUNDARY_REQUIRED",
|
|
2941
|
+
message: "无法跳过:工程边界(allowedPaths / forbiddenPaths / verify)尚未确认,请在访谈中填写或通过 CLI flags 提供",
|
|
2733
2942
|
details: {
|
|
2734
2943
|
gaps: draftSummary(saved.draft).gaps,
|
|
2735
2944
|
acceptedRecommendations: accepted,
|