@tea-agent/loop-agent 0.36.4-beta.0 → 0.37.1-beta.0

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 (118) hide show
  1. package/AGENTS.md +2 -6
  2. package/CHANGELOG.md +72 -163
  3. package/README.md +1 -1
  4. package/bin/loop-agent.js +1 -37
  5. package/dist/application/task-lifecycle/advance.js +0 -1
  6. package/dist/application/task-lifecycle/observe.js +0 -15
  7. package/dist/application/task-lifecycle/plan-transitions.js +0 -15
  8. package/dist/cli/command-definitions.js +0 -7
  9. package/dist/cli/program.js +3 -8
  10. package/dist/commands/init-upgrade.js +19 -351
  11. package/dist/commands/init.js +67 -14
  12. package/dist/commands/run-dag-progress.js +0 -14
  13. package/dist/commands/task-advance.js +3 -33
  14. package/dist/executors/dag-pi-executor.js +30 -47
  15. package/dist/executors/pi-sdk-executor.js +0 -34
  16. package/dist/executors/shell-executor.js +95 -6
  17. package/dist/infrastructure/harness/atomic-write.js +1 -2
  18. package/dist/shared/operator/capabilities.js +13 -200
  19. package/dist/shared/package-metadata.js +0 -42
  20. package/dist/task/frontend-project-capability.js +12 -162
  21. package/dist/task/source-prepare/completeness.js +0 -17
  22. package/dist/task/source-prepare/parse-intent.js +1 -15
  23. package/dist/task/source-prepare/semantic-intake.js +23 -144
  24. package/dist/worker/console/chat/artifact-card.js +1 -8
  25. package/dist/worker/console/chat/chat-event-store.js +0 -61
  26. package/dist/worker/console/chat/human-gate-card.js +1 -9
  27. package/dist/worker/console/chat/operation-card.js +2 -71
  28. package/dist/worker/console/chat/pi-runtime.js +62 -86
  29. package/dist/worker/console/chat/routes.js +8 -36
  30. package/dist/worker/console/chat/session-store.js +0 -3
  31. package/dist/worker/console/chat/shortcuts.js +7 -9
  32. package/dist/worker/console/chat/turn-process.js +23 -97
  33. package/dist/worker/console/chat/workspace-landing.js +1 -2
  34. package/dist/worker/console/operation-runner.js +6 -155
  35. package/dist/worker/console/operator-actions.js +13 -373
  36. package/dist/worker/console/operator-user-error.js +0 -4
  37. package/dist/worker/console/recovery-cta.js +3 -50
  38. package/dist/worker/console/static/assets/index-HX1pbOyl.css +1 -0
  39. package/dist/worker/console/static/assets/index-M0BLEBfh.js +56 -0
  40. package/dist/worker/console/static/index.html +2 -2
  41. package/dist/worker/console/static-src/app/console-types.js +9 -13
  42. package/dist/worker/console/static-src/app/useOperatorActions.js +2 -4
  43. package/dist/worker/console/static-src/app/useRecoveryActions.js +56 -65
  44. package/dist/worker/console/static-src/app/useRecoveryConsole.js +1 -73
  45. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +30 -89
  46. package/dist/worker/console/static-src/operator-chat/refs.js +0 -3
  47. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  48. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +11 -30
  49. package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -12
  50. package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -45
  51. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -7
  52. package/dist/worker/loop-agent/loop-agent-client.js +3 -17
  53. package/dist/worker/observability/read-model.js +0 -20
  54. package/dist/worker/observe/spec-evidence.js +8 -3
  55. package/dist/worker/observe/static/operator-chrome.d.ts +0 -1
  56. package/dist/worker/observe/static/operator-chrome.js +1 -6
  57. package/dist/worker/observe/static/views/dag-inspector.js +71 -6
  58. package/dist/worker/observe/static/views/dag.js +0 -12
  59. package/dist/worker/preflight.js +1 -2
  60. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +707 -37
  61. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  62. package/dist/workflows/dag/backend-test-markdown-workflow.js +109 -7
  63. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  64. package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
  65. package/dist/workflows/dag/backend-test-scenario-param.js +890 -164
  66. package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
  67. package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
  68. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -9
  69. package/dist/workflows/dag/failure-routing.js +4 -9
  70. package/dist/workflows/dag/frontend-implementation-contract.js +39 -233
  71. package/dist/workflows/dag/frontend-prewrite-gate.js +62 -394
  72. package/dist/workflows/dag/frontend-repair.js +18 -219
  73. package/dist/workflows/dag/frontend-verification-trace.js +32 -47
  74. package/dist/workflows/dag/init-hybrid.js +86 -238
  75. package/dist/workflows/dag/lifecycle.js +0 -4
  76. package/dist/workflows/dag/node-execution.js +6 -105
  77. package/dist/workflows/dag/recovery-recommendation.js +0 -58
  78. package/dist/workflows/dag/report.js +0 -6
  79. package/dist/workflows/dag/retry-policy.js +0 -11
  80. package/dist/workflows/dag/runner.js +12 -314
  81. package/dist/workflows/dag/scheduler.js +3 -257
  82. package/dist/workflows/dag/types.js +9 -132
  83. package/dist/workflows/dag/validate.js +2 -7
  84. package/docs/README.md +3 -3
  85. package/docs/architecture/evolution.md +67 -102
  86. package/docs/templates/backend-test-dag.json +50 -32
  87. package/docs/templates/frontend-design-contract.md +14 -34
  88. package/docs/templates/frontend-task-constraints.md +13 -33
  89. package/docs/templates/frontend-task-requirement.md +20 -54
  90. package/docs/templates/init-managed-agents.md +2 -5
  91. package/harness.json +2 -2
  92. package/package.json +3 -4
  93. package/skills/loop-agent/SKILL.md +0 -1
  94. package/skills/loop-agent/references/command-reference.md +0 -3
  95. package/dist/build-stamp.json +0 -6
  96. package/dist/commands/dag-request-interrupt.js +0 -20
  97. package/dist/worker/console/chat/assistant-content.js +0 -121
  98. package/dist/worker/console/chat/semantic-activity.js +0 -471
  99. package/dist/worker/console/operation-run-facts.js +0 -190
  100. package/dist/worker/console/operation-wait.js +0 -355
  101. package/dist/worker/console/recovery-error-copy.js +0 -198
  102. package/dist/worker/console/static/assets/index-D83DYAFG.css +0 -1
  103. package/dist/worker/console/static/assets/index-IXm7oYjL.js +0 -59
  104. package/dist/worker/console/static-src/operator-chat/activity-journey.js +0 -125
  105. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  106. package/dist/worker/console/static-src/operator-chat/activity-references.js +0 -20
  107. package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +0 -24
  108. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
  109. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
  110. package/dist/worker/observability/interrupt-eligibility.js +0 -264
  111. package/dist/workflows/dag/contract-output-registry.js +0 -14
  112. package/dist/workflows/dag/contract-validator-registrations.js +0 -8
  113. package/dist/workflows/dag/frontend-recovery-plan.js +0 -73
  114. package/dist/workflows/dag/frontend-recovery-root-manifest.js +0 -123
  115. package/dist/workflows/dag/frontend-recovery-run.js +0 -539
  116. package/dist/workflows/dag/frontend-writer-recovery.js +0 -106
  117. package/dist/workflows/dag/frontend-writer-rollback.js +0 -821
  118. package/dist/workflows/dag/interrupt-request.js +0 -559
@@ -18,23 +18,18 @@ import { normalizeConsoleWorkflowKind } from "./workflow-kinds.js";
18
18
  import { deriveTaskIdentityFromPrd, nextTaskIdRevision, } from "./prd-identity.js";
19
19
  import { appendEngineeringCliArgs, buildDraftFromTaskIntake, parseEngineeringBoundaryFromParams, } from "./prd-intake-bridge.js";
20
20
  import { discoverPrdReferences } from "./prd-reference-discovery.js";
21
- import { clearSemanticIntakeAttempt, SEMANTIC_INTAKE_CLI_TIMEOUT_MS, } from "../../task/source-prepare/semantic-intake.js";
21
+ import { clearSemanticIntakeAttempt } from "../../task/source-prepare/semantic-intake.js";
22
22
  import nodePath from "node:path";
23
23
  import { resolveSiblingAgentWorkerBin } from "./sibling-controller.js";
24
24
  import { projectOperationEventSummary, projectOperationForChat, } from "./chat/chat-event-store.js";
25
- import { OperationWaitError, waitForOperationChange, } from "./operation-wait.js";
26
25
  import { MutationGateReceiptStore } from "./mutation-gate-receipt-store.js";
27
- import { formatIneligibleRerunMessage } from "./recovery-error-copy.js";
28
- import { evaluateConsoleDagInterruptEligibility } from "../observability/interrupt-eligibility.js";
29
- import { INTERRUPT_ERROR_MESSAGES, isInterruptReasonCode, normalizeInterruptReasonDetail, } from "../observability/interrupt-eligibility.js";
30
26
  import { assessAutonomousExecutionEligibility, deriveEligibilityFactsFromDagSpec, } from "./dag-execution-receipt.js";
31
27
  import { issueHumanGateToken, mutationGatePayloadHash, verifyHumanGateToken, } from "./human-gate-token.js";
32
28
  const MUTATION_GATE_ACTIONS = new Set([
33
29
  // 2026-08-11: standaloneTaskRerun / workerTaskRetry are autonomous (server
34
- // run-facts checks). Destructive DAG reconciliation, live cooperative
35
- // interrupt, and Night Scheduler mutations still need the browser Human Gate.
30
+ // run-facts checks). Destructive DAG reconciliation and Night Scheduler
31
+ // mutations still need the browser Human Gate.
36
32
  "dagReconcileRun",
37
- "dagRequestInterrupt",
38
33
  "workerAdmissionPrepare",
39
34
  "workerSchedulerAdd",
40
35
  "workerSchedulerCancel",
@@ -47,12 +42,10 @@ const MUTATION_OR_LONG = new Set([
47
42
  "contractApply",
48
43
  "contractAdopt",
49
44
  "contractRecover",
50
- "taskAdvance",
51
45
  "dagRunTask",
52
46
  "runDag",
53
47
  "dagRerun",
54
48
  "dagReconcileRun",
55
- "dagRequestInterrupt",
56
49
  "standaloneTaskRerun",
57
50
  "workerTaskRetry",
58
51
  ]);
@@ -62,13 +55,6 @@ function paramsOf(req) {
62
55
  function str(v) {
63
56
  return typeof v === "string" && v.trim() ? v.trim() : undefined;
64
57
  }
65
- const TASK_ID_PLACEHOLDER_RE = /[<>]|^(?:id|task-?id|your-task-id|todo|tbd)$/i;
66
- const TASK_ADVANCE_ALIAS_ACTIONS = new Set(["promoteRun", "closeoutTask"]);
67
- function taskIdInputError(taskId) {
68
- if (!TASK_ID_PLACEHOLDER_RE.test(taskId))
69
- return undefined;
70
- return `taskId must be a concrete canonical id; placeholder values are not allowed: ${JSON.stringify(taskId)}`;
71
- }
72
58
  /** Extract write-set gate token from task advance operator JSON. */
73
59
  function extractWriteSetGateTokenFromCliJson(json, taskId) {
74
60
  if (!json || typeof json !== "object")
@@ -143,46 +129,13 @@ function gateError(action, code, message, details) {
143
129
  }),
144
130
  };
145
131
  }
146
- /**
147
- * `dag report --json` is `{ schemaVersion, runs: [...] }` (no Operator `ok`).
148
- * `runReadCli` used to wrap that as `{ stdout, raw }`, which made the
149
- * autonomous rerun gate miss `primaryFailure` even after the UI showed the
150
- * report (2026-08-16). Accept both the native report shape and the wrap.
151
- */
152
- function extractDagReportRunFacts(reportResult, runId) {
153
- const asRecord = (value) => value && typeof value === "object" && !Array.isArray(value)
154
- ? value
155
- : undefined;
156
- const result = asRecord(reportResult);
157
- const envelope = Array.isArray(result?.runs)
158
- ? result
159
- : asRecord(result?.raw);
160
- if (!envelope)
161
- return undefined;
162
- const runs = Array.isArray(envelope.runs) ? envelope.runs : [];
163
- const matched = runs.find((entry) => asRecord(entry)?.runId === runId) ?? runs[0];
164
- const matchedRec = asRecord(matched);
165
- if (matchedRec?.primaryFailure) {
166
- return {
167
- primaryFailure: asRecord(matchedRec.primaryFailure),
168
- primaryRecovery: asRecord(matchedRec.primaryRecovery),
169
- };
170
- }
171
- if (envelope.primaryFailure) {
172
- return {
173
- primaryFailure: asRecord(envelope.primaryFailure),
174
- primaryRecovery: asRecord(envelope.primaryRecovery),
175
- };
176
- }
177
- return undefined;
178
- }
179
132
  /**
180
133
  * 2026-08-11 R2 run-facts check for standaloneTaskRerun (read-only CLI
181
134
  * evidence only — no browser Human Gate). Fails closed when facts are missing.
182
135
  */
183
136
  async function assertAutonomousRerunEligible(ctx, runId, taskId) {
184
137
  const report = await runReadCli(ctx, ["dag", "report", "--run-id", runId, "--json"], "dag report");
185
- const reportResult = extractDagReportRunFacts(report.ok ? report.result : undefined, runId);
138
+ const reportResult = (report.ok ? report.result : undefined);
186
139
  const recovery = reportResult?.primaryRecovery;
187
140
  if (!report.ok || !reportResult?.primaryFailure || !recovery) {
188
141
  return gateError("standaloneTaskRerun", "INVALID_INPUT", "cannot read dagReport run facts; standaloneTaskRerun requires server-side run evidence", { runId });
@@ -328,9 +281,6 @@ async function runReadCli(ctx, args, command) {
328
281
  if (env.schemaVersion === 1 && typeof env.ok === "boolean") {
329
282
  return env;
330
283
  }
331
- if (raw.schemaVersion === 1 && Array.isArray(raw.runs)) {
332
- return operatorSucceeded(command, raw);
333
- }
334
284
  if (command === "dag rerun plan" &&
335
285
  raw.plan &&
336
286
  typeof raw.plan === "object") {
@@ -342,8 +292,9 @@ async function runReadCli(ctx, args, command) {
342
292
  ? plan.blockedReasons.map(String)
343
293
  : [];
344
294
  const blockingNodes = Array.isArray(plan.blockingNodes)
345
- ? plan.blockingNodes
295
+ ? plan.blockingNodes.map((n) => [n.nodeId, n.reasonCode].filter(Boolean).join(":"))
346
296
  : [];
297
+ const reasons = [...blockedReasons, ...blockingNodes].filter(Boolean);
347
298
  return buildOperatorResult({
348
299
  command,
349
300
  ok: false,
@@ -351,10 +302,9 @@ async function runReadCli(ctx, args, command) {
351
302
  result: plan,
352
303
  error: {
353
304
  code: "DAG_RERUN_INELIGIBLE",
354
- message: formatIneligibleRerunMessage({
355
- blockedReasons,
356
- blockingNodes,
357
- }),
305
+ message: reasons.length > 0
306
+ ? `从节点重跑不具备安全资格:${reasons.slice(0, 6).join("; ")}${reasons.length > 6 ? "…" : ""}`
307
+ : "从节点重跑不具备安全资格(eligible=false)",
358
308
  details: {
359
309
  eligible: false,
360
310
  planHash: typeof plan.planHash === "string" ? plan.planHash : undefined,
@@ -569,90 +519,6 @@ export async function dispatchOperatorAction(ctx, req) {
569
519
  }),
570
520
  };
571
521
  }
572
- case "operationWait": {
573
- const operationId = str(p.operationId);
574
- if (!operationId)
575
- return invalid(action, "operationId is required");
576
- const rawAfterSeq = p.afterSeq ?? 0;
577
- const afterSeq = typeof rawAfterSeq === "number" ? rawAfterSeq : Number(rawAfterSeq);
578
- const rawMaxWaitMs = p.maxWaitMs;
579
- const maxWaitMs = typeof rawMaxWaitMs === "number"
580
- ? rawMaxWaitMs
581
- : rawMaxWaitMs === undefined
582
- ? undefined
583
- : Number(rawMaxWaitMs);
584
- const rawWakeOn = str(p.wakeOn);
585
- const wakeOn = rawWakeOn === "all" || rawWakeOn === "meaningful" ? rawWakeOn : undefined;
586
- try {
587
- const result = await waitForOperationChange({
588
- operations: ctx.operations,
589
- events: ctx.events,
590
- operationId,
591
- afterSeq,
592
- maxWaitMs,
593
- ...(wakeOn ? { wakeOn } : {}),
594
- poll: async () => {
595
- const current = await ctx.operations.get(operationId);
596
- const runId = current?.dagRunId ??
597
- (typeof current?.actionParams?.runId === "string"
598
- ? current.actionParams.runId
599
- : undefined);
600
- if (!current || !runId)
601
- return false;
602
- const eligibility = await evaluateConsoleDagInterruptEligibility({
603
- repoRoot: ctx.repoRoot,
604
- operations: ctx.operations,
605
- runId,
606
- });
607
- if (!eligibility.interrupt)
608
- return false;
609
- const targetId = eligibility.targetOperationId ?? current.operationId;
610
- return syncTargetInterruptProjection({
611
- operations: ctx.operations,
612
- events: ctx.events,
613
- operationId: targetId,
614
- runId,
615
- interrupt: eligibility.interrupt,
616
- });
617
- },
618
- });
619
- return {
620
- kind: "sync",
621
- status: 200,
622
- body: operatorSucceeded(action, result),
623
- };
624
- }
625
- catch (error) {
626
- if (error instanceof OperationWaitError) {
627
- if (error.code === "NOT_FOUND") {
628
- return {
629
- kind: "error",
630
- status: 404,
631
- body: operatorFailed({
632
- command: action,
633
- outcome: "not-found",
634
- code: "NOT_FOUND",
635
- message: error.message,
636
- }),
637
- };
638
- }
639
- if (error.code === "EVENT_CURSOR_EXPIRED") {
640
- return {
641
- kind: "error",
642
- status: 410,
643
- body: operatorFailed({
644
- command: action,
645
- outcome: "blocked",
646
- code: "EVENT_CURSOR_EXPIRED",
647
- message: error.message,
648
- }),
649
- };
650
- }
651
- return invalid(action, error.message);
652
- }
653
- throw error;
654
- }
655
- }
656
522
  case "inspect": {
657
523
  const body = await runReadCli(ctx, ["inspect", "--json"], "inspect");
658
524
  return { kind: "sync", status: body.ok ? 200 : 400, body };
@@ -1320,24 +1186,8 @@ export async function dispatchOperatorAction(ctx, req) {
1320
1186
  };
1321
1187
  }
1322
1188
  const reviewPacket = extractReviewPacketFromCliJson(gen.json) ?? {};
1323
- let parsedDagSpec;
1324
- try {
1325
- parsedDagSpec = JSON.parse(dagText);
1326
- }
1327
- catch (error) {
1328
- return {
1329
- kind: "error",
1330
- status: 409,
1331
- body: operatorFailed({
1332
- command: action,
1333
- outcome: "blocked",
1334
- code: "DAG_GENERATION_FAILED",
1335
- message: `generated DAG is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
1336
- }),
1337
- };
1338
- }
1339
1189
  const derivedFacts = deriveEligibilityFactsFromDagSpec({
1340
- dagSpec: parsedDagSpec,
1190
+ dagSpec: JSON.parse(dagText),
1341
1191
  allowedPaths,
1342
1192
  forbiddenPaths,
1343
1193
  });
@@ -1499,9 +1349,6 @@ export async function dispatchOperatorAction(ctx, req) {
1499
1349
  if (!taskId || !clientRequestId) {
1500
1350
  return invalid(action, "taskId and clientRequestId are required");
1501
1351
  }
1502
- const taskIdError = taskIdInputError(taskId);
1503
- if (taskIdError)
1504
- return invalid(action, taskIdError);
1505
1352
  return acceptOperation(ctx, {
1506
1353
  action,
1507
1354
  actionParams: p,
@@ -1510,38 +1357,6 @@ export async function dispatchOperatorAction(ctx, req) {
1510
1357
  cliArgs: ["task", "advance", taskId, title, "--json"],
1511
1358
  });
1512
1359
  }
1513
- case "taskAdvance": {
1514
- const taskId = str(p.taskId);
1515
- const clientRequestId = str(req.clientRequestId);
1516
- if (!taskId || !clientRequestId) {
1517
- return invalid(action, "taskId and clientRequestId are required");
1518
- }
1519
- const taskIdError = taskIdInputError(taskId);
1520
- if (taskIdError)
1521
- return invalid(action, taskIdError);
1522
- const args = ["task", "advance", taskId];
1523
- const title = str(p.title);
1524
- if (title)
1525
- args.push(title);
1526
- const taskKind = str(p.taskKind);
1527
- if (taskKind)
1528
- args.push("--task-kind", taskKind);
1529
- const featureId = str(p.featureId);
1530
- if (featureId)
1531
- args.push("--feature-id", featureId);
1532
- const profile = str(p.profile);
1533
- if (profile)
1534
- args.push("--profile", profile);
1535
- appendEngineeringCliArgs(args, parseEngineeringBoundaryFromParams(p));
1536
- args.push("--json");
1537
- return acceptOperation(ctx, {
1538
- action,
1539
- actionParams: p,
1540
- clientRequestId,
1541
- taskId,
1542
- cliArgs: args,
1543
- });
1544
- }
1545
1360
  case "bootstrapFromPrd":
1546
1361
  return dispatchBootstrapFromPrd(ctx, p);
1547
1362
  case "importPrd": {
@@ -1552,9 +1367,6 @@ export async function dispatchOperatorAction(ctx, req) {
1552
1367
  if (!taskId || !content) {
1553
1368
  return invalid(action, "taskId and content are required");
1554
1369
  }
1555
- const taskIdError = taskIdInputError(taskId);
1556
- if (taskIdError)
1557
- return invalid(action, taskIdError);
1558
1370
  const taskKindRaw = str(p.taskKind)?.trim();
1559
1371
  const taskKind = normalizeConsoleWorkflowKind(taskKindRaw, "standard");
1560
1372
  const taskKindExplicit = Boolean(taskKindRaw);
@@ -1608,7 +1420,7 @@ export async function dispatchOperatorAction(ctx, req) {
1608
1420
  cwd: ctx.repoRoot,
1609
1421
  artifactName: "import-prd-task-advance-intake",
1610
1422
  expectJson: true,
1611
- timeoutMs: SEMANTIC_INTAKE_CLI_TIMEOUT_MS,
1423
+ timeoutMs: 300_000,
1612
1424
  });
1613
1425
  const intakeJson = intake.json ?? null;
1614
1426
  const intakeOk = intake.ok && intake.exitCode === 0 && !intake.timedOut;
@@ -1749,9 +1561,6 @@ export async function dispatchOperatorAction(ctx, req) {
1749
1561
  if (!taskId || !clientRequestId) {
1750
1562
  return invalid(action, "taskId and clientRequestId are required");
1751
1563
  }
1752
- const taskIdError = taskIdInputError(taskId);
1753
- if (taskIdError)
1754
- return invalid(action, taskIdError);
1755
1564
  const out = await stageUtf8Text(ctx.appData, "", { extension: ".json" });
1756
1565
  return acceptOperation(ctx, {
1757
1566
  action,
@@ -2034,99 +1843,6 @@ export async function dispatchOperatorAction(ctx, req) {
2034
1843
  ],
2035
1844
  });
2036
1845
  }
2037
- case "dagInterruptEligibility": {
2038
- const runId = str(p.runId) ?? str(p.dagRunId);
2039
- if (!runId)
2040
- return invalid(action, "runId is required");
2041
- const eligibility = await evaluateConsoleDagInterruptEligibility({
2042
- repoRoot: ctx.repoRoot,
2043
- operations: ctx.operations,
2044
- runId,
2045
- });
2046
- if (eligibility.interrupt && eligibility.targetOperationId) {
2047
- await syncTargetInterruptProjection({
2048
- operations: ctx.operations,
2049
- events: ctx.events,
2050
- operationId: eligibility.targetOperationId,
2051
- runId,
2052
- interrupt: eligibility.interrupt,
2053
- });
2054
- }
2055
- return {
2056
- kind: "sync",
2057
- status: 200,
2058
- body: operatorSucceeded(action, eligibility),
2059
- };
2060
- }
2061
- case "dagRequestInterrupt": {
2062
- const runId = str(p.runId);
2063
- const reasonCode = str(p.reasonCode);
2064
- const reasonDetail = str(p.reasonDetail) ?? str(p.reason);
2065
- const clientRequestId = str(req.clientRequestId);
2066
- if (!runId || !reasonCode || !reasonDetail || !clientRequestId) {
2067
- return invalid(action, "runId, reasonCode, reasonDetail, and clientRequestId are required");
2068
- }
2069
- if (!isInterruptReasonCode(reasonCode)) {
2070
- return invalid(action, `reasonCode must be one of requirements-changed | suspected-runaway | resource-protection | operator-request | other`);
2071
- }
2072
- try {
2073
- normalizeInterruptReasonDetail(reasonDetail);
2074
- }
2075
- catch (error) {
2076
- return invalid(action, error instanceof Error ? error.message : String(error));
2077
- }
2078
- const gated = await consumeMutationGateReceipt(ctx, action, p);
2079
- if (gated.kind === "error")
2080
- return gated;
2081
- if (gated.kind === "idempotent")
2082
- return gated.result;
2083
- const eligibility = await evaluateConsoleDagInterruptEligibility({
2084
- repoRoot: ctx.repoRoot,
2085
- operations: ctx.operations,
2086
- runId,
2087
- });
2088
- if (!eligibility.eligible || !eligibility.targetOperationId || !eligibility.expectedRunnerIdentity) {
2089
- const failed = interruptActionFailed(action, eligibility.reasonCodes[0] ?? "RUN_NOT_ACTIVE", eligibility.message);
2090
- return failed;
2091
- }
2092
- const expected = eligibility.expectedRunnerIdentity;
2093
- const accepted = await acceptOperation(ctx, {
2094
- action,
2095
- actionParams: {
2096
- runId,
2097
- reasonCode,
2098
- reasonDetail,
2099
- targetOperationId: eligibility.targetOperationId,
2100
- expectedRunnerIdentity: expected,
2101
- },
2102
- clientRequestId,
2103
- cliArgs: [
2104
- "dag",
2105
- "request-interrupt",
2106
- "--run-id",
2107
- runId,
2108
- "--request-id",
2109
- clientRequestId,
2110
- "--target-operation-id",
2111
- eligibility.targetOperationId,
2112
- "--reason-code",
2113
- reasonCode,
2114
- "--reason",
2115
- reasonDetail,
2116
- "--expected-runner-pid",
2117
- String(expected.pid),
2118
- "--expected-runner-hostname",
2119
- expected.hostname,
2120
- "--expected-runner-started-at",
2121
- expected.startedAt,
2122
- "--expected-controller-fingerprint",
2123
- expected.controllerFingerprint,
2124
- "--json",
2125
- ],
2126
- });
2127
- await finalizeMutationGateReceipt(ctx, gated.receiptId, accepted);
2128
- return accepted;
2129
- }
2130
1846
  case "dagReconcileRun": {
2131
1847
  // S6 Recovery: orphan/interrupted runs — structured reason is the human
2132
1848
  // confirmation surface (CLI eligibility still fail-closed).
@@ -2420,7 +2136,7 @@ export async function dispatchOperatorAction(ctx, req) {
2420
2136
  return invalid(action, "action and actionParams are required");
2421
2137
  }
2422
2138
  if (!MUTATION_GATE_ACTIONS.has(targetAction)) {
2423
- return invalid(action, "action must be a mutation-gate target (dagReconcileRun | dagRequestInterrupt | workerAdmissionPrepare | workerSchedulerAdd | workerSchedulerCancel | workerSchedulerHarvest | workerSchedulerDiscard)");
2139
+ return invalid(action, "action must be a mutation-gate target (dagReconcileRun | workerAdmissionPrepare | workerSchedulerAdd | workerSchedulerCancel | workerSchedulerHarvest | workerSchedulerDiscard)");
2424
2140
  }
2425
2141
  const secret = ctx.humanGateSecret;
2426
2142
  if (!secret) {
@@ -2479,14 +2195,6 @@ export async function dispatchOperatorAction(ctx, req) {
2479
2195
  rawArgs.some((value) => typeof value !== "string"))) {
2480
2196
  return invalid(action, "args must be an array of strings");
2481
2197
  }
2482
- if (TASK_ADVANCE_ALIAS_ACTIONS.has(action) && Array.isArray(rawArgs)) {
2483
- const aliasTaskId = str(rawArgs[0]);
2484
- const taskIdError = aliasTaskId
2485
- ? taskIdInputError(aliasTaskId)
2486
- : undefined;
2487
- if (taskIdError)
2488
- return invalid(action, taskIdError);
2489
- }
2490
2198
  if (capability.humanConfirmation === "required") {
2491
2199
  const confirmationId = str(p.confirmationId);
2492
2200
  if (!confirmationId)
@@ -2566,9 +2274,6 @@ export async function dispatchOperatorAction(ctx, req) {
2566
2274
  .replace(/^(loop-agent|agent-worker)\s+/, "")
2567
2275
  .split(/\s+/);
2568
2276
  const args = [...prefix, ...(rawArgs ?? [])];
2569
- if (TASK_ADVANCE_ALIAS_ACTIONS.has(action) && !args.includes("--json")) {
2570
- args.push("--json");
2571
- }
2572
2277
  if (capability.kind === "read") {
2573
2278
  if (!args.includes("--json"))
2574
2279
  args.push("--json");
@@ -2991,7 +2696,7 @@ async function dispatchBootstrapFromPrd(ctx, p) {
2991
2696
  cwd: ctx.repoRoot,
2992
2697
  artifactName: "bootstrap-task-advance-intake",
2993
2698
  expectJson: true,
2994
- timeoutMs: SEMANTIC_INTAKE_CLI_TIMEOUT_MS,
2699
+ timeoutMs: 300_000,
2995
2700
  });
2996
2701
  const intakeJson = intake.json ?? null;
2997
2702
  const intakeOk = intake.ok && intake.exitCode === 0 && !intake.timedOut;
@@ -3465,71 +3170,6 @@ function invalid(action, message) {
3465
3170
  }),
3466
3171
  };
3467
3172
  }
3468
- function interruptActionFailed(action, code, message) {
3469
- const blocked = code === "INTERRUPT_ALREADY_PENDING" ||
3470
- code === "INTERRUPT_NEEDS_RECONCILE" ||
3471
- code === "RUN_ALREADY_TERMINAL" ||
3472
- code === "RUN_NOT_CONSOLE_OWNED" ||
3473
- code === "RUNNER_IDENTITY_MISMATCH" ||
3474
- code === "RUN_NOT_ACTIVE";
3475
- return {
3476
- kind: "error",
3477
- status: code === "INVALID_INPUT" ? 400 : 409,
3478
- body: operatorFailed({
3479
- command: action,
3480
- outcome: blocked ? "blocked" : "invalid",
3481
- code,
3482
- message: message || INTERRUPT_ERROR_MESSAGES[code] || message,
3483
- }),
3484
- };
3485
- }
3486
- async function syncTargetInterruptProjection(input) {
3487
- const current = await input.operations.get(input.operationId);
3488
- if (!current)
3489
- return false;
3490
- if (current.interrupt?.status === input.interrupt.status &&
3491
- current.interrupt?.requestId === input.interrupt.requestId) {
3492
- return false;
3493
- }
3494
- const nextStatus = input.interrupt.status;
3495
- if (nextStatus !== "requested" &&
3496
- nextStatus !== "acknowledged" &&
3497
- nextStatus !== "settled" &&
3498
- nextStatus !== "needs-reconcile") {
3499
- return false;
3500
- }
3501
- await input.operations.update(input.operationId, {
3502
- dagRunId: current.dagRunId ?? input.runId,
3503
- interrupt: {
3504
- status: nextStatus,
3505
- ...(input.interrupt.requestId
3506
- ? { requestId: input.interrupt.requestId }
3507
- : {}),
3508
- ...(input.interrupt.reasonCode
3509
- ? { reasonCode: input.interrupt.reasonCode }
3510
- : {}),
3511
- ...(input.interrupt.requestedAt
3512
- ? { requestedAt: input.interrupt.requestedAt }
3513
- : {}),
3514
- ...(input.interrupt.acknowledgedAt
3515
- ? { acknowledgedAt: input.interrupt.acknowledgedAt }
3516
- : {}),
3517
- ...(input.interrupt.settledAt
3518
- ? { settledAt: input.interrupt.settledAt }
3519
- : {}),
3520
- ...(input.interrupt.outcome ? { outcome: input.interrupt.outcome } : {}),
3521
- },
3522
- });
3523
- input.events.append(input.operationId, {
3524
- kind: "state",
3525
- message: `interrupt ${nextStatus}`,
3526
- data: {
3527
- interruptStatus: nextStatus,
3528
- runId: input.runId,
3529
- },
3530
- });
3531
- return true;
3532
- }
3533
3173
  function confirmationErrorStatus(code) {
3534
3174
  if (code === "NOT_FOUND")
3535
3175
  return 404;
@@ -1,12 +1,8 @@
1
- import { rewriteRecoveryOperatorError } from "./recovery-error-copy.js";
2
1
  /** Map internal spawn/controller errors to actionable Chinese copy. */
3
2
  export function formatOperatorUserError(message) {
4
3
  const raw = (message ?? "").trim();
5
4
  if (!raw)
6
5
  return "操作失败";
7
- const recovery = rewriteRecoveryOperatorError(raw);
8
- if (recovery)
9
- return recovery;
10
6
  if (/controller identity changed|package fingerprint changed|entry binary sha256 changed/i.test(raw)) {
11
7
  return "控制器版本已变更(Console 启动后代码被重新编译)。请重启 Console 后再试,一次会话中途不要升级/重建控制器。";
12
8
  }
@@ -63,7 +63,7 @@ const CTA = {
63
63
  requiresReason: true,
64
64
  action: "dagRerun",
65
65
  lane: "dag",
66
- guide: "终端失败默认首选:先 dagRerunPlan,plan 合格后直接执行(无需 Human Gate)。Worker 持有的 run、workspace/controller 漂移或含 unsafe shell 时不要强跑子图",
66
+ guide: "终端失败默认首选:先 dagRerunPlan,plan 合格再 Human Gate 执行;provider 抖动/只读下游优先,勿无理由新开 task",
67
67
  commandHint: "loop-agent dag rerun --run-id <run-id> --from-node <node-id> --plan",
68
68
  },
69
69
  standaloneTaskRerun: {
@@ -72,7 +72,7 @@ const CTA = {
72
72
  requiresReason: true,
73
73
  action: "standaloneTaskRerun",
74
74
  lane: "dag",
75
- guide: "独立 DAG run 整单重跑:仅当 from-node plan 不合格时直调执行,服务端校验 run facts,无需 Human Gate。Worker 持有的 run 请用下方「重新排队 Worker Task」",
75
+ guide: "整单重跑:仅当 from-node plan 不合格(writer/decision/fingerprint)或契约/源真变时;需确认原因",
76
76
  commandHint: "loop-agent dag rerun-task --run-id <run-id> --reason <text> --json",
77
77
  },
78
78
  workerTaskRetry: {
@@ -81,18 +81,9 @@ const CTA = {
81
81
  requiresReason: true,
82
82
  action: "workerTaskRetry",
83
83
  lane: "worker",
84
- guide: "仅 Worker 队列任务失败时直调执行,服务端校验 Failed 状态,无需 Human Gate;不要与 DAG 完整重跑混用",
84
+ guide: "仅 Worker 队列任务失败时使用,不要与 DAG 重跑混用",
85
85
  commandHint: "agent-worker task retry <task-id> --feature-id <feature-id> --repo . --reason <reason>",
86
86
  },
87
- interrupt: {
88
- id: "interrupt",
89
- label: "中止运行",
90
- requiresReason: true,
91
- action: "dagRequestInterrupt",
92
- lane: "dag",
93
- guide: "仅本机 Console 持有的正在运行 DAG:经 Human Gate 登记协作中止,等待当前节点安全收敛。不会立即强杀进程",
94
- commandHint: "loop-agent dag request-interrupt --run-id <run-id> --reason-code operator-request --reason <structured-reason>",
95
- },
96
87
  };
97
88
  const MATRIX = {
98
89
  "validation-failed": ["report", "doctor", "regenerate"],
@@ -120,7 +111,6 @@ const MATRIX = {
120
111
  ],
121
112
  "terminal-succeeded": ["report", "observeLink"],
122
113
  "needs-reconcile": ["report", "doctor", "reconcile", "regenerate"],
123
- "running-active": ["interrupt", "report", "doctor", "observeLink"],
124
114
  };
125
115
  const FORBIDDEN_LABELS = [
126
116
  "直接改代码",
@@ -128,38 +118,6 @@ const FORBIDDEN_LABELS = [
128
118
  "Cancel",
129
119
  "cancel operation",
130
120
  ];
131
- const TERMINAL_OPERATION_STATES = new Set([
132
- "succeeded",
133
- "failed",
134
- "timed-out",
135
- ]);
136
- /**
137
- * Orthogonal interrupt progress copy. "已中止" only when run terminal facts and
138
- * the target operation exit are both confirmed; otherwise keep requested /
139
- * converging / reconcile wording.
140
- */
141
- export function interruptProgressCopy(input) {
142
- const status = (input.interruptStatus ?? "").trim();
143
- if (!status || status === "none")
144
- return null;
145
- if (status === "requested")
146
- return "请求已登记,等待 runner 确认。";
147
- if (status === "acknowledged") {
148
- return "等待当前节点安全收敛(不会立即强杀)。";
149
- }
150
- if (status === "needs-reconcile") {
151
- return "需要对账:中止请求未能确认。";
152
- }
153
- if (status === "settled") {
154
- const terminalRun = input.dagStatus === "failed" || input.dagStatus === "partial_failed";
155
- const interrupted = (input.failureCategory ?? "").includes("controller-interrupted");
156
- const operationExited = TERMINAL_OPERATION_STATES.has(input.targetOperationState ?? "");
157
- if (terminalRun && interrupted && operationExited)
158
- return "已中止。";
159
- return "正在对账,尚未确认退出。";
160
- }
161
- return null;
162
- }
163
121
  /**
164
122
  * Return allowed recovery CTAs for a fact/failure class.
165
123
  * Never includes Cancel or primary "edit code".
@@ -215,8 +173,6 @@ export function recommendedRecoveryCtaId(factClass) {
215
173
  case "needs-reconcile":
216
174
  // Prefer doctor first; reconcile is available when operator confirms reason.
217
175
  return pick("doctor", "reconcile", "report");
218
- case "running-active":
219
- return pick("interrupt", "doctor", "report");
220
176
  case "runtime-mismatch":
221
177
  case "contract-drift":
222
178
  return pick("doctor", "report");
@@ -295,9 +251,6 @@ export function classifyRecoveryFact(input) {
295
251
  state.includes("suspected-stall")) {
296
252
  return "orphan-unknown";
297
253
  }
298
- if (dag === "running" || dag === "active") {
299
- return "running-active";
300
- }
301
254
  if (state === "succeeded" ||
302
255
  state === "completed" ||
303
256
  state === "finished" ||