@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
@@ -743,7 +743,6 @@ function findDoctorFailureNode(state) {
743
743
  nodeId: state.pausedByNodeId,
744
744
  status: node?.status,
745
745
  rawFailureCategory: node?.failureCategory,
746
- skippedReason: node?.skippedReason,
747
746
  };
748
747
  }
749
748
  const errorEntry = Object.entries(state.nodes).find(([, node]) => node.status === "ERROR");
@@ -759,7 +758,6 @@ function findDoctorFailureNode(state) {
759
758
  nodeId: selected[0],
760
759
  status: selected[1].status,
761
760
  rawFailureCategory: selected[1].failureCategory,
762
- skippedReason: selected[1].skippedReason,
763
761
  };
764
762
  }
765
763
  async function readRunOwnedBackendTestClassification(runDir) {
@@ -788,7 +786,6 @@ async function resolveDoctorFailureRouting(input) {
788
786
  normalizedFailureCategory: "unknown",
789
787
  nodeId: input.nodeId,
790
788
  productLineFailureCategory: classifiedCategory,
791
- skippedReason: input.skippedReason,
792
789
  });
793
790
  }
794
791
  return routeDagFailure(input);
@@ -810,7 +807,6 @@ async function formatDagDoctorMarkdown(repoRoot, runId) {
810
807
  rawFailureCategory,
811
808
  normalizedFailureCategory: normalizedCategory,
812
809
  nodeId: failure.nodeId,
813
- skippedReason: failure.skippedReason,
814
810
  });
815
811
  const evidence = failure.nodeId
816
812
  ? path.join(located.runDir, failure.nodeId, "result.summary.md")
@@ -4,7 +4,6 @@ import { readFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { pathMatchesPattern } from "../../shared/git-progress.js";
6
6
  import { recordDecisionEnvelopeForNode, shouldPauseOnHumanEscalation, writeHumanEscalationArtifacts, } from "./decision-envelope.js";
7
- import { FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT, FRONTEND_WRITER_NODE_IDS, isFrontendWriterAuthorized, readFrontendPrewriteResult, } from "./scheduler.js";
8
7
  import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
9
8
  import { resolveContextPolicy } from "./context-policy.js";
10
9
  import { buildDagNodePromptEnvelope, formatConvergenceFeedbackBlock, } from "./prompt.js";
@@ -13,8 +12,6 @@ import { buildOutputLimitRecoverySection, loadBackendTestWriterProgressForRetry,
13
12
  import { computeBackoffDelayMs, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, } from "./retry-policy.js";
14
13
  import { applyNodeActivity, evaluateNodeLiveness, resolveLivenessPolicy, } from "./liveness-policy.js";
15
14
  import { buildProtocolRetryInstruction, normalizeReviewVerdictAfterRetries, parseJsonReviewVerdict, validateOutputProtocol, } from "./output-protocol.js";
16
- import { getStructuredContractValidator } from "./contract-output-registry.js";
17
- import "./contract-validator-registrations.js";
18
15
  import { writeDagNodeJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
19
16
  import { buildProjectGovernanceContext, readCompletedWriterChangeManifests, writeProjectGovernanceContext, } from "./project-governance-context.js";
20
17
  import { assertSkillSnapshotCoversSpec, buildNodePromptFromSnapshot, isDagSkillSnapshotIntegrityError, readSkillSnapshot, } from "./skill-snapshot.js";
@@ -141,20 +138,6 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
141
138
  buildProtocolRetryInstruction(task.outputProtocol, previousProtocolReason),
142
139
  ].join("\n");
143
140
  }
144
- if (previousFailureCategory === "invalid-output" &&
145
- task.structuredContractOutput &&
146
- previousProtocolReason) {
147
- return [
148
- basePrompt,
149
- "",
150
- "<retry_instruction>",
151
- "Previous attempt produced an invalid frontend implementation contract:",
152
- previousProtocolReason,
153
- "Return exactly one fenced json block conforming to the frontend-implementation-contract-v1 schema.",
154
- "Fix every reported field violation: do not emit null for optional fields, do not misspell field names, and match the required types exactly.",
155
- "</retry_instruction>",
156
- ].join("\n");
157
- }
158
141
  if (previousFailureCategory === "writer-empty-diff") {
159
142
  const maxAttempts = task.retryPolicy?.maxAttempts ?? 3;
160
143
  // When a completeness progress exists for this writer, fold the concrete
@@ -216,28 +199,18 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
216
199
  "</retry_instruction>",
217
200
  ].join("\n");
218
201
  }
219
- if (previousFailureCategory !== "output-too-large") {
202
+ if (task.outputMode !== "structured-required" ||
203
+ previousFailureCategory !== "output-too-large") {
220
204
  return basePrompt;
221
205
  }
222
- if (task.outputMode === "structured-required") {
223
- return [
224
- basePrompt,
225
- "",
226
- "<retry_instruction>",
227
- "Previous attempt exceeded the structured output size limit.",
228
- "Return only the compact structured artifact required by this node's output contract.",
229
- "Do not include explanatory prose, duplicated upstream context, long evidence excerpts, or additional markdown sections.",
230
- "If a fenced JSON object is required, output exactly one fenced json block and nothing else.",
231
- "</retry_instruction>",
232
- ].join("\n");
233
- }
234
206
  return [
235
207
  basePrompt,
236
208
  "",
237
209
  "<retry_instruction>",
238
- "Previous attempt exceeded the output size limit.",
239
- "Return a minimal plan: ordered steps, narrow writeSet boundaries, and verification commands.",
240
- "Drop verbatim upstream quotes, long evidence excerpts, and repeated context.",
210
+ "Previous attempt exceeded the structured output size limit.",
211
+ "Return only the compact structured artifact required by this node's output contract.",
212
+ "Do not include explanatory prose, duplicated upstream context, long evidence excerpts, or additional markdown sections.",
213
+ "If a fenced JSON object is required, output exactly one fenced json block and nothing else.",
241
214
  "</retry_instruction>",
242
215
  ].join("\n");
243
216
  }
@@ -422,21 +395,6 @@ export async function executeDagNode(input) {
422
395
  await notifyNodeObserver(input.observer, "onNodeFinish", nodeId, state);
423
396
  };
424
397
  const failSkillSnapshot = (error) => failBeforePrompt(error, "skill-snapshot-integrity");
425
- const skipFrontendWriter = async (admission) => {
426
- const skippedAt = new Date().toISOString();
427
- node.startedAt ??= skippedAt;
428
- node.frontendWriterAdmission = admission;
429
- node.status = "SKIPPED";
430
- node.skippedReason = "frontend-prewrite-not-authorized";
431
- node.finishedAt = skippedAt;
432
- node.lastActivityAt = skippedAt;
433
- node.durationMs = durationBetween(node.startedAt, node.finishedAt);
434
- node.timing = { retryBackoffMs: 0, settlementCleanupMs: 0 };
435
- state.nodes[nodeId].nodeRecordPath = path.join(runDir, `${nodeId}.json`);
436
- await writeNodeRecord(runDir, nodeId, state.nodes[nodeId]);
437
- await input.persistState();
438
- await notifyNodeObserver(input.observer, "onNodeFinish", nodeId, state);
439
- };
440
398
  if (task.finalWriteSetApproval) {
441
399
  const authorization = parseAndValidateFinalWriteSetApproval({ task, spec, state });
442
400
  if (!authorization.ok) {
@@ -465,30 +423,6 @@ export async function executeDagNode(input) {
465
423
  effectiveWriteSet: [...authorization.effectiveWriteSet],
466
424
  };
467
425
  }
468
- if (FRONTEND_WRITER_NODE_IDS.includes(nodeId)) {
469
- const admission = await readFrontendPrewriteResult(runDir);
470
- if (!admission.ok) {
471
- await skipFrontendWriter(undefined);
472
- return;
473
- }
474
- const decision = isFrontendWriterAuthorized(admission.result);
475
- const record = {
476
- schemaVersion: 1,
477
- writerNodeId: nodeId,
478
- decision,
479
- sourceArtifact: FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT,
480
- artifactHash: admission.artifactHash,
481
- checkedAt: new Date().toISOString(),
482
- reason: decision === "denied"
483
- ? `classification: ${admission.result.classification}`
484
- : null,
485
- };
486
- if (decision === "denied") {
487
- await skipFrontendWriter(record);
488
- return;
489
- }
490
- node.frontendWriterAdmission = record;
491
- }
492
426
  let projectGovernanceContext;
493
427
  if (task.governanceStandardReview) {
494
428
  try {
@@ -755,39 +689,6 @@ export async function executeDagNode(input) {
755
689
  previousProtocolReason = undefined;
756
690
  }
757
691
  }
758
- // R1: structured contract nodes self-validate their output so schema,
759
- // typo, and null violations surface as retryable invalid-output at the
760
- // producing node instead of failing the whole run at the prewrite gate.
761
- if (result.ok && task.structuredContractOutput) {
762
- const validator = getStructuredContractValidator(task.structuredContractOutput.schemaId);
763
- if (validator) {
764
- const contractText = canonicalNodeOutput(result);
765
- const contractCheck = await validator({
766
- runDir,
767
- text: contractText,
768
- sourceBinding: spec.sourceBinding,
769
- });
770
- if (!contractCheck.ok) {
771
- result = {
772
- ...result,
773
- ok: false,
774
- failureCategory: "invalid-output",
775
- stderr: [result.stderr, contractCheck.reason]
776
- .filter(Boolean)
777
- .join("\n"),
778
- };
779
- previousProtocolReason = contractCheck.reason;
780
- }
781
- else {
782
- previousProtocolReason = undefined;
783
- }
784
- }
785
- else {
786
- // DAG spec validation rejects unknown schemaIds; this is a
787
- // defensive fallback for a registry that has not been populated.
788
- console.warn(`[run-dag] warning: no structured contract validator registered for schemaId ${task.structuredContractOutput.schemaId}; skipping node self-check`);
789
- }
790
- }
791
692
  const attemptFinishedAt = new Date().toISOString();
792
693
  const attemptWallDurationMs = durationBetween(attemptStartedAt, attemptFinishedAt);
793
694
  totalAttemptWallDurationMs += attemptWallDurationMs;
@@ -269,61 +269,3 @@ export function planDagRecovery(input) {
269
269
  }
270
270
  return planByNormalizedCategory(input);
271
271
  }
272
- /**
273
- * Phase 6: route a frontend recovery outcome to a recovery CTA (read-only; never
274
- * mutates a run's status). The six outcomes map to the existing recovery actions
275
- * so the Console recovery page reuses the current CTA surface.
276
- */
277
- export function recommendFrontendRecoveryOutcome(outcome) {
278
- switch (outcome) {
279
- case "none":
280
- return {
281
- action: "none",
282
- summary: "未发生前端恢复;按普通终态展示。",
283
- reason: "该 frontend-implementation 根没有触发自动恢复。",
284
- humanRequired: false,
285
- autoRetryEligible: false,
286
- };
287
- case "recovered":
288
- return {
289
- action: "none",
290
- summary: "自动恢复成功;child 已重新实现并通过验证。",
291
- reason: "parent 的 transient 失败已由 child 恢复,全链按 root 计一次成功。",
292
- humanRequired: false,
293
- autoRetryEligible: false,
294
- };
295
- case "candidate-contract-invalid":
296
- return {
297
- action: "manual-review",
298
- summary: "方案输出无效:candidate JSON 无法解析/推导,重试用尽。",
299
- reason: "建议人工检查需求或重新生成方案后再跑。",
300
- humanRequired: true,
301
- autoRetryEligible: false,
302
- };
303
- case "prewrite-blocked":
304
- return {
305
- action: "manual-review",
306
- summary: "治理门禁阻断:source stale / writeSet 越界 / Mock policy / contract 违规。",
307
- reason: "需人工修正任务边界/事实后重跑,不自动重试。",
308
- humanRequired: true,
309
- autoRetryEligible: false,
310
- };
311
- case "repair-exhausted":
312
- return {
313
- action: "rerun-after-fix",
314
- summary: "自动修复未通过:verify 失败,1 次 repair 后 reverify 仍失败。",
315
- reason: "建议 `dag rerun` 或人工排查。",
316
- humanRequired: true,
317
- autoRetryEligible: false,
318
- commandHint: "loop-agent dag rerun --run-id <runId>",
319
- };
320
- case "auto-recovery-blocked":
321
- return {
322
- action: "manual-review",
323
- summary: "自动恢复被阻止:rollback 无法证明完整或存在外部/越界修改。",
324
- reason: "必须人工介入,禁止自动覆盖用户文件。",
325
- humanRequired: true,
326
- autoRetryEligible: false,
327
- };
328
- }
329
- }
@@ -441,7 +441,6 @@ export async function buildDagRunReportEntry(input) {
441
441
  normalizedFailureCategory,
442
442
  nodeId,
443
443
  executor: node.executor,
444
- skippedReason: node.skippedReason,
445
444
  });
446
445
  const followUp = node.status === "ERROR" || node.status === "SKIPPED"
447
446
  ? recommendFollowUpForFailureCategory(node.failureCategory)
@@ -506,7 +505,6 @@ export async function buildDagRunReportEntry(input) {
506
505
  normalizedFailureCategory: normalizeDagFailureCategory(pausedNode.failureCategory, pausedNode.status),
507
506
  failureCategory: pausedNode.failureCategory,
508
507
  nodeId: input.state.pausedByNodeId,
509
- skippedReason: pausedNode.skippedReason,
510
508
  }
511
509
  : firstActionableNode
512
510
  ? {
@@ -515,7 +513,6 @@ export async function buildDagRunReportEntry(input) {
515
513
  normalizeDagFailureCategory(firstActionableNode.failureCategory, firstActionableNode.status),
516
514
  failureCategory: firstActionableNode.failureCategory,
517
515
  nodeId: firstActionableNode.nodeId,
518
- skippedReason: input.state.nodes[firstActionableNode.nodeId]?.skippedReason,
519
516
  }
520
517
  : {
521
518
  status: input.state.status,
@@ -532,9 +529,6 @@ export async function buildDagRunReportEntry(input) {
532
529
  rawFailureCategory: runRecoverySource.failureCategory,
533
530
  normalizedFailureCategory: runRecoverySource.normalizedFailureCategory,
534
531
  nodeId: "nodeId" in runRecoverySource ? runRecoverySource.nodeId : undefined,
535
- skippedReason: "skippedReason" in runRecoverySource
536
- ? runRecoverySource.skippedReason
537
- : undefined,
538
532
  });
539
533
  const runFollowUp = input.state.status === "failed" ||
540
534
  input.state.status === "partial_failed"
@@ -118,17 +118,6 @@ export const STRUCTURED_REQUIRED_PI_RETRY_POLICY = {
118
118
  ...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
119
119
  retryCategories: [...STRUCTURED_REQUIRED_DAG_RETRY_CATEGORIES],
120
120
  };
121
- /**
122
- * Planner read-only nodes (standard DAG contract-pi / plan-pi) may retry with
123
- * a compact-output instruction when the model produced an oversized assistant
124
- * response. Only `output-too-large` is added on top of the default set;
125
- * report/review and other read-only roles keep the default set so they do not
126
- * silently learn new output semantics.
127
- */
128
- export const PLANNER_OUTPUT_LIMIT_RETRY_POLICY = {
129
- ...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
130
- retryCategories: [...DEFAULT_DAG_RETRY_CATEGORIES, STRUCTURED_OUTPUT_RETRY_CATEGORY],
131
- };
132
121
  /**
133
122
  * Default retry for reviewer / recovery nodes that declare outputProtocol.
134
123
  * Includes protocol-invalid so missing VERDICT lines are corrected in-node.