@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.
Files changed (28) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/application/task-lifecycle/advance.js +57 -7
  3. package/dist/application/task-lifecycle/plan-transitions.js +13 -21
  4. package/dist/executors/dag-pi-executor.js +18 -3
  5. package/dist/shared/operator/capabilities.js +26 -2
  6. package/dist/task/source-prepare/prepare.js +49 -2
  7. package/dist/worker/console/interview/grill-me.js +7 -6
  8. package/dist/worker/console/operator-actions.js +231 -22
  9. package/dist/worker/console/prd-intake-bridge.js +393 -0
  10. package/dist/worker/console/recovery-cta.js +35 -6
  11. package/dist/worker/console/static/assets/index-BpuHmlSP.js +29 -0
  12. package/dist/worker/console/static/index.html +1 -1
  13. package/dist/worker/console/static-src/app/console-types.js +1 -0
  14. package/dist/worker/console/static-src/app/usePrdImport.js +2 -1
  15. package/dist/worker/console/static-src/app/useRecoveryActions.js +24 -1
  16. package/dist/worker/console/static-src/app/useRecoveryConsole.js +19 -1
  17. package/dist/worker/console/static-src/app/useTaskWizard.js +57 -2
  18. package/dist/worker/observability/read-model.js +3 -0
  19. package/dist/workflows/dag/failure-category.js +3 -0
  20. package/dist/workflows/dag/init-hybrid.js +15 -9
  21. package/dist/workflows/dag/node-execution.js +3 -2
  22. package/dist/workflows/dag/retry-policy.js +44 -11
  23. package/dist/workflows/dag/runner.js +6 -0
  24. package/dist/workflows/dag/scheduler.js +49 -1
  25. package/dist/workflows/dag/validate.js +16 -2
  26. package/docs/templates/agent-dag.schema.json +4 -4
  27. package/package.json +1 -1
  28. 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); only Night Scheduler mutations still need the browser
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
- const clientRequestId = str(req.clientRequestId);
1360
- if (!taskId || !content || !clientRequestId) {
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
- return acceptOperation(ctx, {
1367
- action,
1368
- actionParams: { taskId, stagedSha256: staged.sha256 },
1369
- clientRequestId,
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
- // PRD archive via task advance; create if missing.
1372
- cliArgs: ["task", "advance", taskId, "--prd", staged.path, "--json"],
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
- const draftSaved = await ctx.drafts.save({
2499
- ...emptyDraft(taskId, title, { taskKind }),
2500
- requirement: {
2501
- objective: title,
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: accept recommendations for remaining gaps and
2701
- * issue a complete assessment so contractApply can proceed.
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: "INVALID_INPUT",
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,