@tea-agent/loop-agent 0.22.0 → 0.23.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 (73) hide show
  1. package/AGENTS.md +42 -108
  2. package/CHANGELOG.md +68 -0
  3. package/README.md +7 -4
  4. package/dist/application/context-usage/skill-resolution-stats.js +263 -0
  5. package/dist/application/dag/generate-task-dag.js +17 -3
  6. package/dist/cli/command-definitions.js +8 -7
  7. package/dist/cli/program.js +17 -15
  8. package/dist/commands/doctor.js +269 -18
  9. package/dist/commands/init.js +101 -86
  10. package/dist/commands/stats.js +40 -11
  11. package/dist/shared/operator/capabilities.js +11 -1
  12. package/dist/worker/console/app-data.js +4 -0
  13. package/dist/worker/console/chat/instruction-skills.js +217 -0
  14. package/dist/worker/console/chat/model-resolver.js +106 -0
  15. package/dist/worker/console/chat/pi-runtime.js +605 -0
  16. package/dist/worker/console/chat/resource-loader.js +66 -0
  17. package/dist/worker/console/chat/routes.js +357 -0
  18. package/dist/worker/console/chat/session-store.js +238 -0
  19. package/dist/worker/console/chat/tool-adapter.js +238 -0
  20. package/dist/worker/console/chat/tools.js +171 -0
  21. package/dist/worker/console/server.js +55 -0
  22. package/dist/worker/console/static/assets/index-DVl7Jxt5.js +25 -0
  23. package/dist/worker/console/static/assets/index-lVcIr9Ju.css +1 -0
  24. package/dist/worker/console/static/index.html +2 -2
  25. package/dist/worker/feature/profile-schema.js +1 -1
  26. package/dist/workflows/dag/init-hybrid.js +69 -21
  27. package/dist/workflows/dag/node-execution.js +38 -1
  28. package/dist/workflows/dag/output-protocol.js +89 -0
  29. package/dist/workflows/dag/prompt.js +35 -1
  30. package/dist/workflows/dag/recovery-recommendation.js +45 -0
  31. package/dist/workflows/dag/report.js +28 -1
  32. package/dist/workflows/dag/rerun-task.js +1 -1
  33. package/dist/workflows/dag/scheduler.js +9 -0
  34. package/dist/workflows/dag/types.js +12 -0
  35. package/dist/workflows/dag/validate.js +55 -0
  36. package/docs/README.md +73 -156
  37. package/docs/architecture/README.md +3 -2
  38. package/docs/architecture/dag-execution.md +2 -2
  39. package/docs/architecture/evolution.md +14 -12
  40. package/docs/architecture/system-overview.md +1 -1
  41. package/docs/architecture/worker-and-feature.md +3 -3
  42. package/docs/governance/README.md +15 -0
  43. package/docs/{harness-methodology-debugging.md → governance/harness-methodology-debugging.md} +27 -3
  44. package/docs/init-surface.manifest.json +22 -4
  45. package/docs/operations/README.md +12 -0
  46. package/docs/{local-development-environment.md → operations/local-development-environment.md} +1 -1
  47. package/docs/skills/vetted-skill-registry.md +23 -3
  48. package/docs/templates/README.md +55 -0
  49. package/docs/templates/evaluation/agents-map-slim-v1.candidate.json +9 -0
  50. package/docs/templates/evaluation/agents-map-slim-v1.md +87 -0
  51. package/docs/templates/evaluation/agents-map-verbose-v0.candidate.json +9 -0
  52. package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -0
  53. package/docs/templates/hybrid-dag.json +1 -1
  54. package/docs/templates/progress-log.md +9 -2
  55. package/harness.json +5 -5
  56. package/package.json +5 -5
  57. package/scripts/kb-bootstrap-init-skeleton.sh +2 -2
  58. package/skills/agent-worker/SKILL.md +1 -1
  59. package/skills/grill-with-docs/SKILL.md +44 -52
  60. package/skills/grill-with-docs/adr-format.md +37 -26
  61. package/skills/grill-with-docs/context-format.md +18 -26
  62. package/skills/loop-agent/SKILL.md +28 -112
  63. package/skills/loop-agent/references/command-reference.md +9 -3
  64. package/skills/loop-agent/references/harness-policy.md +3 -3
  65. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  66. package/skills/loop-agent/references/orchestrator-and-interventions.md +1 -1
  67. package/skills/loop-agent/references/task-workflow.md +2 -0
  68. package/skills/systematic-debugging/SKILL.md +20 -4
  69. package/skills/test-driven-development/SKILL.md +10 -3
  70. package/dist/worker/console/static/assets/index-CUDke82y.js +0 -18
  71. package/dist/worker/console/static/assets/index-wSEksVSO.css +0 -1
  72. /package/docs/{harness-methodology-tdd.md → governance/harness-methodology-tdd.md} +0 -0
  73. /package/docs/{harness-methodology-verification.md → governance/harness-methodology-verification.md} +0 -0
@@ -0,0 +1 @@
1
+ :root{--canvas: #f7f7f4;--canvas-soft: #fafaf7;--surface: #ffffff;--surface-muted: #f0efe9;--ink: #26251e;--body: #5a5852;--muted: #807d72;--muted-soft: #a09c92;--hairline: #e6e5e0;--hairline-strong: #cfcdc4;--orange: #f54e00;--orange-active: #d04200;--orange-soft: color-mix(in srgb, var(--orange) 12%, white);--blue: #3f6ea4;--green: #1f8a65;--amber: #a66a00;--red: #c72d53;--peach: #f1d8cb;--mint: #d8ead5;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-8: 32px;--radius-sm: 6px;--radius-md: 8px;--radius-lg: 12px;--radius-pill: 999px;--transition-fast: .16s ease;--transition-base: .22s ease;--shadow-panel: 0 1px 2px rgb(38 37 30 / 4%), 0 8px 24px rgb(38 37 30 / 3%);--shadow-soft: 0 10px 30px rgb(38 37 30 / 6%);color-scheme:light;font-family:Segoe UI,PingFang SC,Hiragino Sans GB,Noto Sans SC,system-ui,-apple-system,sans-serif;line-height:1.5}*{box-sizing:border-box}html{background:radial-gradient(1200px 480px at 8% -10%,rgb(241 216 203 / 45%),transparent 55%),radial-gradient(900px 420px at 100% 0%,rgb(216 234 213 / 35%),transparent 50%),var(--canvas);min-height:100%}body{margin:0;min-height:100vh;min-width:320px;background:transparent;color:var(--ink);font-size:14px}button,input,select,textarea{font:inherit}a{color:inherit}code,.cmd,.kv dd code{font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace}.embedded-operator-nav{position:sticky;top:0;z-index:40;display:flex;gap:6px;align-items:center;margin:0;padding:8px max(24px,calc((100vw - 1120px)/2));border-bottom:1px solid var(--hairline);background:color-mix(in srgb,var(--canvas) 92%,transparent);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);font-size:13px;font-weight:700}.embedded-operator-nav a,.embedded-operator-nav span{padding:6px 12px;border-radius:var(--radius-pill);text-decoration:none;color:var(--ink)}.embedded-operator-nav a:hover,.embedded-operator-nav a:focus-visible{background:var(--surface);color:var(--orange-active);outline:none}.embedded-operator-nav span[aria-current=page]{background:var(--ink);color:#fff}.console-shell{width:min(100% - 48px,1120px);margin:0 auto;padding:var(--space-5) 0 var(--space-12, 48px)}.console-header{display:grid;gap:var(--space-4);grid-template-columns:1fr auto;align-items:center;margin-bottom:var(--space-4);padding-bottom:var(--space-4);border-bottom:1px solid var(--hairline)}.brand-block{display:grid;gap:2px;align-content:start;min-width:0}h1{margin:0;color:var(--ink);font-size:clamp(20px,2.2vw,24px);font-weight:650;line-height:1.2;letter-spacing:-.02em}.subtitle{max-width:58ch;margin:0;color:var(--body);font-size:13px;line-height:1.4}.top-nav{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);margin:0 0 var(--space-5);padding:6px;border:1px solid var(--hairline);border-radius:var(--radius-pill);background:color-mix(in srgb,var(--surface) 88%,transparent);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:var(--shadow-panel)}.nav-link{appearance:none;display:inline-flex;align-items:center;min-height:34px;padding:0 14px;border:1px solid transparent;border-radius:var(--radius-pill);background:transparent;color:var(--body);font-size:13px;font-weight:600;cursor:pointer;transition:background-color var(--transition-fast),color var(--transition-fast),border-color var(--transition-fast),transform var(--transition-fast)}.nav-link:hover:not(:disabled),.nav-link:focus-visible:not(:disabled){background:var(--surface);color:var(--ink);outline:none}.nav-link-active{background:var(--ink);color:#fff}.nav-link-active:hover:not(:disabled),.nav-link-active:focus-visible:not(:disabled){background:var(--ink);color:#fff}.nav-link-external{margin-left:auto;border-color:var(--hairline);background:var(--surface);color:var(--orange-active)}.nav-link-external:hover:not(:disabled){border-color:var(--orange);background:var(--orange-soft);color:var(--orange-active)}.readiness{position:relative;display:grid;gap:2px;min-width:140px;max-width:280px;border:1px solid var(--hairline);background:var(--surface);border-radius:var(--radius-md);padding:8px 32px 8px 12px;box-shadow:var(--shadow-panel)}.readiness:before{content:"";position:absolute;top:50%;right:12px;width:8px;height:8px;border-radius:50%;transform:translateY(-50%);background:var(--muted-soft)}.readiness[data-state=setup-required]{border-color:var(--hairline);background:var(--surface)}.readiness[data-state=setup-required]:before{background:var(--amber)}.readiness[data-state=ready]{border-color:var(--hairline);background:var(--surface)}.readiness[data-state=ready]:before{background:color-mix(in srgb,var(--green) 72%,white);box-shadow:0 0 0 3px color-mix(in srgb,var(--green) 12%,transparent)}.readiness[data-state=degraded]{border-color:var(--hairline);background:var(--surface)}.readiness[data-state=degraded]:before{background:var(--amber);box-shadow:0 0 0 3px color-mix(in srgb,var(--amber) 12%,transparent)}.readiness-label{display:block;margin:0;color:var(--muted);font-size:11px;font-weight:600;line-height:1.2}.readiness strong{color:var(--ink);font-size:14px;font-weight:700;line-height:1.2}.readiness[data-state=ready] strong,.readiness[data-state=setup-required] strong,.readiness[data-state=degraded] strong{color:var(--ink)}.readiness .muted{margin:2px 0 0;font-size:12px;line-height:1.35}.flash{display:flex;gap:var(--space-3);align-items:flex-start;margin:0 0 var(--space-4);padding:12px 14px;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface);box-shadow:var(--shadow-panel);animation:flash-in .22s ease}.flash-error{border-color:color-mix(in srgb,var(--red) 35%,var(--hairline));background:color-mix(in srgb,var(--red) 6%,var(--surface));color:var(--red)}.flash-info{border-color:color-mix(in srgb,var(--blue) 30%,var(--hairline));background:color-mix(in srgb,var(--blue) 6%,var(--surface));color:var(--body)}.flash-label{flex:0 0 auto;min-width:3.5rem;font-size:12px;font-weight:700;letter-spacing:.02em}.flash-body{margin:0;overflow-wrap:anywhere}.muted{color:var(--muted)}.console-main{display:grid;gap:var(--space-4)}.inspect-split-panel{position:fixed;z-index:50;top:16px;right:16px;bottom:16px;width:min(420px,calc(100vw - 32px));display:grid;align-content:start;gap:var(--space-4);padding:var(--space-5);overflow:auto;border:1px solid var(--hairline);border-radius:var(--radius-lg);background:var(--surface);box-shadow:var(--shadow-soft)}.inspect-split-nodes{display:grid;gap:6px;margin:0;padding:0;list-style:none;max-height:220px;overflow:auto}.inspect-split-nodes li{display:grid;grid-template-columns:1fr auto;gap:var(--space-2);align-items:center;padding:6px 8px;border:1px solid var(--hairline);border-radius:var(--radius-sm);background:var(--canvas-soft);font-size:12px}.inspect-split-nodes li.is-active{border-color:var(--orange);background:var(--orange-soft)}.inspect-split-nodes code{color:var(--muted);font-size:11px}.inspect-split-header{display:flex;align-items:start;justify-content:space-between;gap:var(--space-3)}.inspect-split-header h2,.inspect-split-panel h3{margin:0}.inspect-split-kicker,.inspect-split-id{margin:0;color:var(--muted);font-size:12px}.inspect-split-facts{display:grid;gap:var(--space-2);margin:0}.inspect-split-facts div{display:grid;grid-template-columns:7rem 1fr;gap:var(--space-2);padding-bottom:var(--space-2);border-bottom:1px solid var(--hairline)}.inspect-split-facts dt{color:var(--muted)}.inspect-split-facts dd{margin:0;overflow-wrap:anywhere}.inspect-split-events{display:grid;gap:8px;margin:0;padding-left:18px;color:var(--body);font-size:12px}.panel{min-width:0;border:1px solid var(--hairline);background:color-mix(in srgb,var(--surface) 96%,transparent);border-radius:var(--radius-lg);padding:var(--space-6);box-shadow:var(--shadow-panel)}.panel h2{margin:0 0 var(--space-2);color:var(--ink);font-size:18px;font-weight:700;letter-spacing:-.01em}.panel-lead{margin:0 0 var(--space-5);color:var(--muted);font-size:13px}.panel h3{margin:0 0 var(--space-3);color:var(--ink);font-size:15px;font-weight:700}.empty-state{border:1px dashed var(--hairline-strong);border-radius:var(--radius-md);padding:var(--space-6);background:var(--canvas-soft);color:var(--body)}.empty-title{margin:0 0 var(--space-2);color:var(--ink);font-size:16px;font-weight:700}.checklist{margin:0 0 var(--space-4);padding:var(--space-4);border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--canvas-soft);list-style:none;color:var(--body)}.checklist li{position:relative;padding:6px 0 6px 22px}.checklist li:before{content:"";position:absolute;left:0;top:12px;width:8px;height:8px;border-radius:50%;background:var(--orange);opacity:.75}.kv{display:grid;gap:var(--space-1);margin:0}.kv div{display:grid;grid-template-columns:9rem 1fr;gap:var(--space-3);padding:10px 0;border-bottom:1px solid var(--hairline)}.kv div:last-child{border-bottom:none}.kv dt{color:var(--muted);font-size:12px;font-weight:600}.kv dd{margin:0;color:var(--ink);overflow-wrap:anywhere}code{font-size:.85rem;color:var(--ink)}.cmd{display:block;width:100%;margin:0;padding:var(--space-4);border:1px solid var(--hairline);border-radius:var(--radius-md);background:#f3efe8;color:var(--ink);font-size:12px;line-height:1.55;overflow-x:auto;white-space:pre-wrap;word-break:break-word;max-height:280px}.external-link{color:var(--orange);text-decoration:none;font-weight:600}.external-link:hover{text-decoration:underline}.error{color:var(--red)}.wizard-steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(108px,1fr));gap:var(--space-2);list-style:none;padding:0;margin:0 0 var(--space-5)}.wizard-steps li{position:relative;min-height:52px;display:grid;align-content:center;gap:2px;border:1px solid var(--hairline);border-radius:var(--radius-md);padding:8px 10px;background:var(--canvas-soft);color:var(--muted);font-size:12px;font-weight:650;transition:border-color var(--transition-fast),background-color var(--transition-fast),color var(--transition-fast),box-shadow var(--transition-fast)}.wizard-steps li[data-done=true]{border-color:color-mix(in srgb,var(--green) 35%,var(--hairline));background:color-mix(in srgb,var(--mint) 45%,var(--surface));color:var(--green)}.wizard-steps li[data-active=true]{border-color:var(--orange);background:var(--surface);color:var(--ink);box-shadow:0 0 0 3px var(--orange-soft)}.wizard-steps .step-index{font-size:10px;font-weight:700;letter-spacing:.04em;color:var(--muted-soft)}.wizard-steps li[data-active=true] .step-index,.wizard-steps li[data-done=true] .step-index{color:inherit;opacity:.8}.wizard-steps .step-label{line-height:1.25}.field{display:grid;gap:var(--space-1);margin:0 0 var(--space-4);width:min(100%,520px)}.field span{font-size:12px;font-weight:600;color:var(--muted)}.field input,.field textarea,.field select{width:100%;border:1px solid var(--hairline-strong);border-radius:var(--radius-md);background:var(--surface);color:var(--ink);transition:border-color var(--transition-fast),box-shadow var(--transition-fast)}.field input{min-height:40px;padding:0 var(--space-3)}.field select{min-height:40px;padding:0 var(--space-3);cursor:pointer}.field textarea{min-height:120px;padding:var(--space-3);resize:vertical}.field input:focus-visible,.field textarea:focus-visible,.field select:focus-visible{outline:none;border-color:var(--orange);box-shadow:0 0 0 3px var(--orange-soft)}.field input:disabled,.field textarea:disabled,.field select:disabled{background:var(--surface-muted);color:var(--muted);cursor:not-allowed}.workflow-hint{margin:calc(var(--space-1) * -1) 0 var(--space-3);color:var(--muted);font-size:12px}.meta-row{display:grid;gap:6px;margin:0 0 var(--space-4);padding:var(--space-3) var(--space-4);border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--canvas-soft);font-size:13px}.meta-row code{font-size:12px}.cta-row,.recovery-cta-row{display:flex;flex-wrap:wrap;gap:var(--space-2);align-items:center}.cta-row{margin-top:var(--space-3)}.cta-row .cmd{flex:1 1 100%}.wizard-body{display:grid;gap:var(--space-3);justify-items:stretch;animation:panel-in .24s ease}.wizard-body>div{display:grid;gap:var(--space-3);justify-items:stretch}.wizard-body .field{width:100%;max-width:640px}.wizard-body .cta-row{margin-top:var(--space-1)}.interview-card{width:100%;padding:var(--space-5);border:1px solid var(--hairline);border-radius:var(--radius-lg);background:linear-gradient(180deg,var(--surface),var(--canvas-soft));box-shadow:var(--shadow-panel)}.interview-q{margin:0 0 var(--space-2);color:var(--ink)}.btn,button{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:40px;padding:0 16px;border:1px solid var(--hairline-strong);border-radius:var(--radius-md);background:var(--surface);color:var(--ink);font-size:13px;font-weight:650;cursor:pointer;transition:border-color var(--transition-fast),background-color var(--transition-fast),color var(--transition-fast),box-shadow var(--transition-fast),transform var(--transition-fast)}.btn:hover:not(:disabled),button:hover:not(:disabled){border-color:color-mix(in srgb,var(--ink) 18%,var(--hairline-strong));background:var(--canvas-soft);outline:none}.btn:focus-visible:not(:disabled),button:focus-visible:not(:disabled){outline:none;box-shadow:0 0 0 3px var(--orange-soft)}.btn:active:not(:disabled),button:active:not(:disabled){transform:translateY(1px)}.btn:disabled,button:disabled{opacity:.48;cursor:not-allowed;transform:none}.btn-primary,button.btn-primary{border-color:var(--orange);background:var(--orange);color:#fff;box-shadow:0 6px 16px #f54e002e}.btn-primary:hover:not(:disabled),button.btn-primary:hover:not(:disabled){border-color:var(--orange-active);background:var(--orange-active);color:#fff}.btn-secondary,button.btn-secondary{border-color:var(--hairline-strong);background:var(--surface);color:var(--ink)}.btn-ghost,button.btn-ghost{border-color:transparent;background:transparent;color:var(--body)}.btn-ghost:hover:not(:disabled),button.btn-ghost:hover:not(:disabled){background:var(--surface-muted);color:var(--ink)}.recovery-cta-row{margin-top:var(--space-3)}.recovery-confirm{margin-top:var(--space-3);padding:var(--space-4);border:1px solid color-mix(in srgb,var(--orange) 40%,var(--hairline));border-radius:var(--radius-md);background:color-mix(in srgb,var(--orange-soft) 55%,var(--surface))}.recovery-confirm h3{margin:0 0 var(--space-2);font-size:15px}.recovery-confirm .cta-row{margin-top:var(--space-3)}.recovery-report{margin-top:var(--space-3);padding:var(--space-4);border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface)}.recovery-report-header{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);margin-bottom:var(--space-3)}.recovery-report-header h3{margin:0;font-size:15px}.recovery-report-facts{display:grid;gap:var(--space-2);margin:0 0 var(--space-3)}.recovery-report-facts div{display:grid;grid-template-columns:5.5rem 1fr;gap:var(--space-2)}.recovery-report-facts dt{color:var(--muted);font-size:12px}.recovery-report-facts dd{margin:0;overflow-wrap:anywhere}.recovery-steps{margin:.5rem 0 .75rem 1.25rem;padding:0;line-height:1.5}.recovery-lane{margin-top:var(--space-4);padding-top:var(--space-3);border-top:1px solid var(--border, #ddd)}.recovery-lane h3{margin:0 0 .35rem;font-size:1rem}.recovery-cta-row button[disabled]{opacity:.4}@keyframes flash-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}@keyframes panel-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}@media(max-width:800px){.console-shell{width:min(100% - 24px,1120px);padding-top:var(--space-5)}.console-header{grid-template-columns:1fr}.nav-link-external{margin-left:0}.inspect-split-panel{inset:auto 0 0;width:100%;max-height:min(76vh,680px);border-radius:var(--radius-lg) var(--radius-lg) 0 0}.kv div{grid-template-columns:1fr}.wizard-steps{grid-template-columns:repeat(2,minmax(0,1fr))}}.oc-shell{display:flex;flex-direction:column;height:100%;min-height:0;gap:var(--space-3);position:relative}.oc-workspace{display:flex;flex:1 1 auto;min-height:0;gap:0;position:relative}.oc-main{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0}.oc-process{display:flex;flex-direction:column;flex:0 0 min(360px,42%);width:min(360px,42%);min-width:280px;min-height:0;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface);box-shadow:var(--shadow-panel);margin-left:var(--space-3);overflow:hidden;animation:panel-in .18s ease}.oc-process-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);padding:10px 12px;border-bottom:1px solid var(--hairline);flex-shrink:0}.oc-process-title{display:inline-flex;align-items:center;gap:8px;font-weight:700;font-size:13px;color:var(--ink)}.oc-process-title-mark{width:8px;height:8px;border-radius:50%;background:var(--orange)}.oc-process-count{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:18px;padding:0 6px;border-radius:var(--radius-pill);background:var(--surface-muted);color:var(--muted);font-size:11px;font-weight:650}.oc-process-close{font-size:12px}.oc-process-timeline{flex:1 1 auto;min-height:0;overflow-y:auto;padding:var(--space-3);display:flex;flex-direction:column;gap:0}.oc-process-empty{padding:var(--space-4);color:var(--muted);font-size:12.5px;line-height:1.5;text-align:center}.oc-process-item{display:grid;grid-template-columns:20px 52px minmax(0,1fr);gap:8px;align-items:start;padding:4px 0}.oc-process-rail{position:relative;display:grid;place-items:center;min-height:34px}.oc-process-rail:before{position:absolute;top:0;bottom:0;width:1px;background:var(--hairline);content:""}.oc-process-item.is-first .oc-process-rail:before{top:50%}.oc-process-item.is-last .oc-process-rail:before{bottom:50%}.oc-process-dot{position:relative;z-index:1;width:8px;height:8px;border-radius:50%;background:var(--surface);border:2px solid color-mix(in srgb,var(--orange) 55%,var(--hairline))}.oc-process-item.is-running .oc-process-dot{border-color:var(--orange-active);background:var(--orange-soft)}.oc-process-item.is-error .oc-process-dot{border-color:var(--red);background:color-mix(in srgb,var(--red) 18%,var(--surface))}.oc-process-time{padding-top:10px;font-size:11px;color:var(--muted-soft);font-variant-numeric:tabular-nums;white-space:nowrap}.oc-process-body{min-width:0}.oc-process-toggle{cursor:pointer;font:inherit}.oc-process-toggle.is-active{border-color:color-mix(in srgb,var(--orange) 40%,var(--hairline));background:color-mix(in srgb,var(--orange) 10%,var(--surface));color:var(--orange-active)}.oc-process-toggle.is-running{border-color:color-mix(in srgb,var(--orange) 40%,var(--hairline));color:var(--orange-active)}.oc-process-toggle.is-error{border-color:color-mix(in srgb,var(--red) 35%,var(--hairline));background:color-mix(in srgb,var(--red) 8%,var(--surface));color:var(--red)}.oc-process-summary{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border:1px solid var(--hairline);border-radius:var(--radius-pill);background:var(--surface);color:var(--muted);font-size:11.5px;font-weight:650;cursor:pointer;font:inherit;max-width:min(420px,100%);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.oc-process-summary:hover,.oc-process-summary.is-open{border-color:color-mix(in srgb,var(--orange) 35%,var(--hairline));color:var(--orange-active)}.oc-process-summary.is-running{color:var(--orange-active);border-color:color-mix(in srgb,var(--orange) 40%,var(--hairline));background:var(--orange-soft)}.oc-process-summary.is-error{color:var(--red);border-color:color-mix(in srgb,var(--red) 35%,var(--hairline));background:color-mix(in srgb,var(--red) 8%,var(--surface))}.oc-process-summary.is-ok{color:var(--green);border-color:color-mix(in srgb,var(--green) 30%,var(--hairline))}@media(max-width:900px){.oc-workspace.has-process .oc-process{position:absolute;top:0;right:0;bottom:0;z-index:8;width:min(360px,calc(100% - 24px));min-width:0;flex:none;margin-left:0;border-radius:var(--radius-md) 0 0 var(--radius-md);box-shadow:var(--shadow-soft)}}.oc-banner{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2) var(--space-3);padding:10px 14px;border:1px solid var(--hairline);border-radius:var(--radius-md);background:color-mix(in srgb,var(--surface) 94%,transparent);box-shadow:var(--shadow-panel)}.oc-banner-title{display:inline-flex;align-items:center;gap:6px;font-weight:700;color:var(--ink)}.oc-banner-dot{width:8px;height:8px;border-radius:50%;background:var(--orange)}.oc-banner-body{color:var(--body);font-size:12.5px;line-height:1.45}.oc-banner-body strong{color:var(--ink)}.oc-cap-chips{display:inline-flex;flex-wrap:wrap;gap:6px;margin-left:auto;align-items:center}.oc-cap-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border:1px solid color-mix(in srgb,var(--green) 30%,var(--hairline));border-radius:var(--radius-pill);background:color-mix(in srgb,var(--mint) 50%,var(--surface));color:var(--green);font-size:11.5px;font-weight:650}button.oc-cap-chip{font:inherit;cursor:pointer}.oc-tools-menu{position:relative}.oc-tools-toggle.is-active{border-color:color-mix(in srgb,var(--orange) 40%,var(--hairline));background:color-mix(in srgb,var(--orange) 10%,var(--surface));color:var(--orange-active)}.oc-tools-catalog{position:absolute;top:calc(100% + 8px);right:0;z-index:20;width:min(420px,calc(100vw - 48px));max-height:min(480px,60vh);display:flex;flex-direction:column;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface);box-shadow:var(--shadow-soft);animation:panel-in .16s ease;overflow:hidden}.oc-tools-catalog-head{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid var(--hairline);flex-shrink:0}.oc-tools-catalog-head strong{font-size:13px;color:var(--ink)}.oc-tools-catalog-meta{font-size:11.5px;color:var(--muted-soft)}.oc-tools-catalog-close{margin-left:auto;font-size:12px}.oc-tools-catalog-body{overflow-y:auto;padding:var(--space-3);display:flex;flex-direction:column;gap:var(--space-3)}.oc-tools-group-title{margin:0 0 8px;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--muted-soft)}.oc-tools-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.oc-tools-item{padding:8px 10px;border:1px solid var(--hairline);border-radius:var(--radius-sm);background:color-mix(in srgb,var(--surface-muted) 55%,var(--surface))}.oc-tools-item-head{display:flex;align-items:center;gap:8px;margin-bottom:4px}.oc-tools-name{font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:12.5px;font-weight:650;color:var(--ink)}.oc-tools-kind{margin-left:auto;font-size:10.5px;font-weight:650;padding:1px 7px;border-radius:var(--radius-pill);color:var(--muted);background:var(--surface);border:1px solid var(--hairline)}.oc-tools-kind.is-explore,.oc-tools-kind.is-read{color:var(--green);border-color:color-mix(in srgb,var(--green) 28%,var(--hairline));background:color-mix(in srgb,var(--mint) 45%,transparent)}.oc-tools-kind.is-mutation{color:var(--orange-active);border-color:color-mix(in srgb,var(--orange) 35%,var(--hairline));background:var(--orange-soft)}.oc-tools-kind.is-long-running{color:var(--amber);border-color:color-mix(in srgb,var(--amber) 35%,var(--hairline));background:color-mix(in srgb,var(--amber) 10%,var(--surface))}.oc-tools-desc{margin:0;font-size:12px;line-height:1.45;color:var(--body)}.oc-tools-cli{margin:4px 0 0;font-size:11px;color:var(--muted-soft)}.oc-tools-cli code{font-size:11px;color:var(--muted)}.oc-alert{padding:8px 12px;border-radius:var(--radius-md);font-size:12.5px;line-height:1.45;animation:panel-in .18s ease}.oc-alert-warning{border:1px solid color-mix(in srgb,var(--amber) 35%,var(--hairline));background:color-mix(in srgb,var(--amber) 8%,var(--surface));color:var(--amber)}.oc-alert-error{border:1px solid color-mix(in srgb,var(--red) 35%,var(--hairline));background:color-mix(in srgb,var(--red) 7%,var(--surface));color:var(--red)}.oc-messages{flex:1 1 auto;min-height:0;overflow-y:auto;padding:var(--space-4) var(--space-3);display:flex;flex-direction:column;gap:var(--space-4)}.oc-day-sep{display:flex;align-items:center;gap:var(--space-3);color:var(--muted-soft);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;margin:var(--space-2) 0}.oc-day-sep:before,.oc-day-sep:after{content:"";flex:1;height:1px;background:var(--hairline)}.oc-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;flex:1;gap:var(--space-4);text-align:center;padding:var(--space-6) var(--space-4)}.oc-empty-icon{display:grid;place-items:center;width:64px;height:64px;border-radius:50%;background:var(--orange-soft);color:var(--orange-active);font-size:30px;font-weight:800;font-family:var(--font-mono, "JetBrains Mono", monospace)}.oc-empty h3{margin:0;font-size:18px;color:var(--ink)}.oc-empty p{margin:0;max-width:46ch;color:var(--muted);font-size:13px;line-height:1.5}.oc-suggestions{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2);width:100%;max-width:640px;margin-top:var(--space-2)}@media(max-width:560px){.oc-suggestions{grid-template-columns:1fr}}.oc-suggestion{appearance:none;display:flex;flex-direction:column;gap:4px;text-align:left;padding:12px 14px;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface);color:var(--ink);cursor:pointer;transition:border-color var(--transition-fast),background var(--transition-fast),transform var(--transition-fast),box-shadow var(--transition-fast)}.oc-suggestion:hover{border-color:var(--orange);background:var(--surface);box-shadow:0 0 0 3px var(--orange-soft);transform:translateY(-1px)}.oc-suggestion strong{font-size:13px;font-weight:700}.oc-suggestion span{color:var(--muted);font-size:12px}.oc-row{display:flex;flex-direction:column;gap:4px;max-width:min(100%,900px)}.oc-row.is-user{align-self:flex-end;align-items:flex-end}.oc-row.is-assistant,.oc-row.is-tool{align-self:flex-start;align-items:stretch;width:min(100%,760px)}.oc-meta{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--muted-soft)}.oc-avatar{display:grid;place-items:center;width:22px;height:22px;border-radius:50%;font-size:11px;font-weight:700;flex-shrink:0}.oc-avatar-user{background:var(--ink);color:#fff}.oc-avatar-assistant{background:var(--orange);color:#fff}.oc-role{font-weight:700;color:var(--body)}.oc-time{font-variant-numeric:tabular-nums}.oc-bubble{padding:10px 14px;border-radius:var(--radius-lg);font-size:14px;line-height:1.6;overflow-wrap:anywhere;word-break:break-word;animation:bubble-in .16s ease}@keyframes bubble-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.oc-bubble-user{background:var(--ink);color:#fff;border-bottom-right-radius:4px}.oc-bubble-assistant{background:var(--surface);color:var(--ink);border:1px solid var(--hairline);border-bottom-left-radius:4px;box-shadow:var(--shadow-panel)}.oc-bubble-tool{background:var(--canvas-soft);color:var(--body);border:1px solid var(--hairline)}.oc-bubble-error{border-color:color-mix(in srgb,var(--red) 40%,var(--hairline));background:color-mix(in srgb,var(--red) 7%,var(--surface));color:var(--red)}.oc-md{display:flex;flex-direction:column;gap:var(--space-2)}.oc-md>*:first-child{margin-top:0}.oc-md>*:last-child{margin-bottom:0}.oc-md p{margin:0}.oc-md h1,.oc-md h2,.oc-md h3,.oc-md h4{margin:var(--space-3) 0 var(--space-2);color:inherit;font-weight:700;line-height:1.3}.oc-md h1{font-size:1.3em}.oc-md h2{font-size:1.18em}.oc-md h3{font-size:1.06em}.oc-md h4{font-size:1em}.oc-md ul,.oc-md ol{margin:0;padding-left:1.4em;display:flex;flex-direction:column;gap:2px}.oc-md li{margin:0}.oc-md blockquote{margin:0;padding:2px 0 2px 12px;border-left:3px solid var(--hairline-strong);color:var(--body)}.oc-bubble-user .oc-md blockquote{border-left-color:#ffffff59;color:#ffffffd1}.oc-md code{font-size:.86em;padding:1px 5px;border-radius:4px;background:color-mix(in srgb,var(--ink) 8%,transparent)}.oc-bubble-user .oc-md code{background:#ffffff29}.oc-md pre{margin:0;padding:0;background:transparent}.oc-codeblock{margin:0;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface-muted);overflow:hidden}.oc-bubble-assistant .oc-codeblock{background:#f3efe8}.oc-codeblock-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);padding:5px 6px 5px 10px;border-bottom:1px solid var(--hairline);font-size:11px;color:var(--muted);background:color-mix(in srgb,var(--ink) 4%,transparent)}.oc-codeblock-lang{font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-weight:600;text-transform:lowercase}.oc-codeblock-copy{appearance:none;display:inline-flex;align-items:center;gap:5px;min-height:26px;padding:0 9px;border:1px solid transparent;border-radius:var(--radius-sm);background:transparent;color:var(--muted);font-size:11px;font-weight:600;cursor:pointer;transition:background var(--transition-fast),color var(--transition-fast),border-color var(--transition-fast)}.oc-codeblock-copy:hover{background:var(--surface);color:var(--ink);border-color:var(--hairline-strong)}.oc-codeblock pre{margin:0;padding:var(--space-3) var(--space-4);overflow-x:auto;font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:12.5px;line-height:1.55;white-space:pre;color:var(--ink)}.oc-md a{color:var(--orange);text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--orange) 40%,transparent)}.oc-md a:hover{border-bottom-color:var(--orange)}.oc-md hr{margin:var(--space-2) 0;border:none;border-top:1px solid var(--hairline-strong)}.oc-stream{white-space:pre-wrap;font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:13px;line-height:1.55}.oc-cursor{display:inline-block;width:7px;height:1.05em;margin-left:2px;vertical-align:text-bottom;background:var(--orange);animation:oc-blink 1s steps(2,start) infinite;border-radius:1px}@keyframes oc-blink{to{opacity:0}}.oc-tool{border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface);overflow:hidden;box-shadow:var(--shadow-panel);animation:panel-in .16s ease}.oc-tool.is-error{border-color:color-mix(in srgb,var(--red) 38%,var(--hairline))}.oc-tool.is-running{border-color:color-mix(in srgb,var(--orange) 40%,var(--hairline))}.oc-tool-head{display:flex;align-items:center;gap:var(--space-2);width:100%;padding:9px 12px;border:none;background:transparent;text-align:left;cursor:pointer;font:inherit;color:var(--ink)}.oc-tool-head:hover{background:var(--surface-muted)}.oc-tool-chevron{flex-shrink:0;width:12px;height:12px;transition:transform var(--transition-fast)}.oc-tool-head[aria-expanded=true] .oc-tool-chevron{transform:rotate(90deg)}.oc-tool-icon{display:grid;place-items:center;width:22px;height:22px;border-radius:6px;background:var(--surface-muted);color:var(--orange-active);flex-shrink:0}.oc-tool-name{font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:12.5px;font-weight:650;flex-shrink:0}.oc-tool-args{color:var(--muted);font-size:11.5px;font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-right:8px;min-width:0}.oc-tool-status{margin-left:auto;flex-shrink:0;font-size:11px;font-weight:650;padding:2px 8px;border-radius:var(--radius-pill)}.oc-tool-waiting{color:var(--muted);font-size:12px;animation:pulse 1.5s infinite}.oc-tool-status.is-running{color:var(--orange-active);background:var(--orange-soft)}.oc-tool-status.is-ok{color:var(--green);background:color-mix(in srgb,var(--mint) 60%,transparent)}.oc-tool-status.is-error{color:var(--red);background:color-mix(in srgb,var(--red) 10%,transparent)}.oc-tool-body{padding:0 var(--space-4) var(--space-3);border-top:1px solid var(--hairline)}.oc-tool-section{display:flex;flex-direction:column;gap:4px;padding-top:var(--space-3)}.oc-tool-label{font-size:11px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--muted-soft)}.oc-tool-code{margin:0;padding:var(--space-3);border-radius:var(--radius-sm);background:var(--surface-muted);font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:12px;line-height:1.5;white-space:pre-wrap;word-break:break-word;max-height:260px;overflow-y:auto;color:var(--body)}.oc-composer{flex-shrink:0;border-top:1px solid var(--hairline);padding:var(--space-3) var(--space-4) var(--space-4);background:color-mix(in srgb,var(--canvas) 86%,transparent)}.oc-composer-inner{display:flex;flex-direction:column;gap:var(--space-2);max-width:1080px;margin:0 auto}.oc-composer-shell{position:relative;display:flex;align-items:flex-end;gap:var(--space-2);padding:8px 8px 8px 14px;border:1px solid var(--hairline-strong);border-radius:var(--radius-lg);background:var(--surface);box-shadow:var(--shadow-panel);transition:border-color var(--transition-fast),box-shadow var(--transition-fast)}.oc-composer-shell:focus-within{border-color:var(--orange);box-shadow:0 0 0 3px var(--orange-soft)}.oc-composer-shell.is-busy{opacity:.78}.oc-textarea{flex:1 1 auto;min-width:0;min-height:52px;max-height:280px;padding:8px 0;border:none;background:transparent;color:var(--ink);font:inherit;font-size:14px;line-height:1.5;resize:none;outline:none}.oc-textarea::placeholder{color:var(--muted-soft)}.oc-send{appearance:none;display:grid;place-items:center;width:38px;height:38px;flex-shrink:0;padding:0;border:1px solid var(--orange);border-radius:50%;background:var(--orange);color:#fff;cursor:pointer;transition:background var(--transition-fast),border-color var(--transition-fast),transform var(--transition-fast)}.oc-send:hover:not(:disabled){background:var(--orange-active);border-color:var(--orange-active)}.oc-send:active:not(:disabled){transform:scale(.94)}.oc-send:disabled{opacity:.4;cursor:not-allowed}.oc-stop{appearance:none;display:grid;place-items:center;width:38px;height:38px;flex-shrink:0;padding:0;border:1px solid var(--hairline-strong);border-radius:50%;background:var(--surface);color:var(--red);cursor:pointer;transition:background var(--transition-fast),border-color var(--transition-fast)}.oc-stop:hover{background:color-mix(in srgb,var(--red) 8%,var(--surface));border-color:var(--red)}.oc-composer-foot{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;font-size:11.5px;color:var(--muted)}.oc-composer-hint{display:inline-flex;align-items:center;gap:5px}.oc-kbd{display:inline-block;padding:1px 6px;border:1px solid var(--hairline-strong);border-bottom-width:2px;border-radius:4px;background:var(--surface);color:var(--body);font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:10.5px;line-height:1.4}.oc-session-info{display:inline-flex;align-items:center;gap:8px;margin-left:auto;font-size:11px;color:var(--muted-soft)}.oc-session-info code{font-size:11px;color:var(--muted)}.oc-new-session{margin-left:auto}@media(max-width:600px){.oc-session-info{width:100%;margin-left:0}}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.01ms!important}}
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Loop 操作台 · Operator Console</title>
7
- <script type="module" crossorigin src="/assets/index-CUDke82y.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-wSEksVSO.css">
7
+ <script type="module" crossorigin src="/assets/index-DVl7Jxt5.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-lVcIr9Ju.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -5,7 +5,7 @@ import { z } from "zod";
5
5
  * `fullstack-v1` is NOT a new DAG or taskKind. It is `agent-worker`'s
6
6
  * deterministic structural validation + read-model projection policy over a
7
7
  * Feature Packet. See
8
- * `docs/design/agent-worker-fullstack-workflow-integration.md` §9.
8
+ * `docs/design/active/agent-worker-fullstack-workflow-integration.md` §9.
9
9
  *
10
10
  * A Feature Packet declares its profile via an optional `feature.yaml` next to
11
11
  * `acceptance.yaml`. A packet without `feature.yaml` (or one that does not set
@@ -18,7 +18,8 @@ import { discoverProjectGovernancePresence } from "./project-governance-context.
18
18
  import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
19
19
  import { materializeTaskReferenceDocs } from "../../task/source-references.js";
20
20
  import { observeTaskContract } from "../../task/contract/observe.js";
21
- import { resolveVerifyPreset } from "../../executors/shell-verification.js";
21
+ import { dagHasWriterExecution } from "./task-contract-binding.js";
22
+ import { DEFAULT_VERIFY_TIMEOUT_MS, resolveVerifyPreset, } from "../../executors/shell-verification.js";
22
23
  import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
23
24
  import { normalizeTaskRequirementText, resolveTaskDagTemplateSelection, } from "./task-demand-routing.js";
24
25
  import { BACKEND_TEST_EXECUTION_DEFAULT_TEST_ROOT, buildBackendTestExecutionPreflightShellSnippet, } from "./backend-test-execution-contract.js";
@@ -556,7 +557,7 @@ export function hasApiDependency(sources) {
556
557
  /**
557
558
  * Resolve frontend Mock mode from capability seed, task config, and interface dependency analysis.
558
559
  *
559
- * Decision matrix (from docs/design/frontend-mock-data-workflow.md):
560
+ * Decision matrix (from docs/design/active/frontend-mock-data-workflow.md):
560
561
  *
561
562
  * | 接口/异步数据依赖 | 既有 Mock 服务 | policy | 结果 |
562
563
  * |---|---|---|---|
@@ -871,21 +872,29 @@ function buildExplicitFrontendVerifyCommands(taskConfig, repoRoot) {
871
872
  function verifyCommandKey(command) {
872
873
  return `${command.cwd}\0${command.args.join("\0")}`;
873
874
  }
874
- function resolveDagVerifyStrategy(taskConfig) {
875
+ function resolveDagVerifyStrategy(taskConfig, defaultIntermediateQuotaWhenFull = "full") {
876
+ const explicitIntermediateQuota = taskConfig.dagVerifyStrategy?.intermediateQuota;
875
877
  return {
876
- intermediateQuota: taskConfig.dagVerifyStrategy?.intermediateQuota ?? taskConfig.verifyQuota,
878
+ intermediateQuota: explicitIntermediateQuota ??
879
+ (taskConfig.verifyQuota === "full"
880
+ ? defaultIntermediateQuotaWhenFull
881
+ : taskConfig.verifyQuota),
877
882
  finalQuota: "full",
878
883
  focusedCommandSource: taskConfig.dagVerifyStrategy?.focusedCommandSource ?? "adapter",
879
884
  };
880
885
  }
881
886
  function buildVerifyEvidence(input) {
887
+ const selectedCommands = input.commands && input.commands.length > 0 ? input.commands : undefined;
888
+ const commandCount = selectedCommands?.length ?? input.fallbackCommands.length;
882
889
  return {
883
890
  phase: input.phase,
884
891
  quota: input.quota,
885
892
  commandSource: input.commandSource,
886
- commandCount: input.commands?.length ?? input.fallbackCommands.length,
887
- commandLabels: input.commands?.map((command) => command.label) ?? input.fallbackCommands,
893
+ commandCount,
894
+ commandLabels: selectedCommands?.map((command) => command.label) ?? input.fallbackCommands,
888
895
  commandTexts: input.commandTexts ?? [],
896
+ commandTimeoutMs: input.commandTimeoutMs,
897
+ totalTimeoutBudgetMs: commandCount * input.commandTimeoutMs,
889
898
  finalFullRequired: input.finalFullRequired,
890
899
  };
891
900
  }
@@ -1369,9 +1378,10 @@ export function buildStandardHybridDagFromTask(sources) {
1369
1378
  commands: finalVerifyCommands,
1370
1379
  fallbackCommands: [],
1371
1380
  finalFullRequired: true,
1381
+ commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
1372
1382
  }),
1373
1383
  cwd: ".",
1374
- timeoutMs: 300000,
1384
+ timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
1375
1385
  },
1376
1386
  },
1377
1387
  ]
@@ -1677,9 +1687,10 @@ function buildFrontendMockVerifyNode(sources, implementId, readOnlyPaths, forbid
1677
1687
  commands: verifyCommands.length > 0 ? verifyCommands : undefined,
1678
1688
  fallbackCommands: [],
1679
1689
  commandTexts: commands,
1690
+ commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
1680
1691
  }),
1681
1692
  cwd: ".",
1682
- timeoutMs: 300000,
1693
+ timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
1683
1694
  },
1684
1695
  };
1685
1696
  }
@@ -2017,6 +2028,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2017
2028
  commands: partitionedStaticVerifyCommands.static.commands,
2018
2029
  fallbackCommands: staticFallbackCommands,
2019
2030
  commandTexts: staticShellCommands,
2031
+ commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
2020
2032
  });
2021
2033
  const lintVerifyEvidence = lintShellCommands.length > 0
2022
2034
  ? buildVerifyEvidence({
@@ -2026,6 +2038,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2026
2038
  commands: partitionedStaticVerifyCommands.lint.commands,
2027
2039
  fallbackCommands: [],
2028
2040
  commandTexts: lintShellCommands,
2041
+ commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
2029
2042
  })
2030
2043
  : undefined;
2031
2044
  const behaviorVerifyEvidence = buildVerifyEvidence({
@@ -2036,6 +2049,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2036
2049
  fallbackCommands: behaviorFallbackCommands,
2037
2050
  commandTexts: behaviorShellCommands,
2038
2051
  finalFullRequired: true,
2052
+ commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
2039
2053
  });
2040
2054
  const mockVerifyTemplate = mockMode === "required" && hasMockVerifyCommands
2041
2055
  ? buildFrontendMockVerifyNode(frontendSources, implementId, readOnlyPaths, forbiddenPaths)
@@ -2273,7 +2287,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2273
2287
  lintEvidence: lintVerifyEvidence,
2274
2288
  },
2275
2289
  cwd: ".",
2276
- timeoutMs: 300000,
2290
+ timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
2277
2291
  },
2278
2292
  },
2279
2293
  ]
@@ -2345,7 +2359,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2345
2359
  mode: "initial",
2346
2360
  },
2347
2361
  cwd: ".",
2348
- timeoutMs: 300000,
2362
+ timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
2349
2363
  },
2350
2364
  },
2351
2365
  {
@@ -2406,7 +2420,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2406
2420
  mode: "repair",
2407
2421
  },
2408
2422
  cwd: ".",
2409
- timeoutMs: 300000,
2423
+ timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
2410
2424
  },
2411
2425
  },
2412
2426
  {
@@ -3122,9 +3136,10 @@ function buildExecuteBackendPytestNode(sources, options = {}) {
3122
3136
  commandSource: "inline",
3123
3137
  fallbackCommands: [pytestCommand],
3124
3138
  finalFullRequired: true,
3139
+ commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
3125
3140
  }),
3126
3141
  cwd: ".",
3127
- timeoutMs: 300000,
3142
+ timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
3128
3143
  },
3129
3144
  };
3130
3145
  }
@@ -3241,6 +3256,7 @@ function buildBackendTestOutcomeGateNode(sources) {
3241
3256
  commandSource: "inline",
3242
3257
  fallbackCommands: [gateCommand],
3243
3258
  finalFullRequired: true,
3259
+ commandTimeoutMs: 60_000,
3244
3260
  }),
3245
3261
  cwd: ".",
3246
3262
  timeoutMs: 60000,
@@ -3981,6 +3997,7 @@ function buildMultiPerspectiveReviewNodes(input) {
3981
3997
  commandSource: "inline",
3982
3998
  fallbackCommands: [aggregateScript],
3983
3999
  finalFullRequired: true,
4000
+ commandTimeoutMs: 60_000,
3984
4001
  }),
3985
4002
  cwd: ".",
3986
4003
  timeoutMs: 60000,
@@ -4165,6 +4182,7 @@ function buildKnowledgeSyncValidateNode(sources, featureId) {
4165
4182
  commandSource: "inline",
4166
4183
  fallbackCommands: [validateScript],
4167
4184
  finalFullRequired: true,
4185
+ commandTimeoutMs: 120_000,
4168
4186
  }),
4169
4187
  cwd: ".",
4170
4188
  timeoutMs: 120000,
@@ -4385,6 +4403,7 @@ function buildKgBootstrapPreflightNode(sources) {
4385
4403
  quota: "full",
4386
4404
  commandSource: "inline",
4387
4405
  fallbackCommands: [script],
4406
+ commandTimeoutMs: 60_000,
4388
4407
  }),
4389
4408
  cwd: ".",
4390
4409
  timeoutMs: 60000,
@@ -4432,6 +4451,7 @@ function buildKgBootstrapInventoryNode(sources) {
4432
4451
  quota: "full",
4433
4452
  commandSource: "inline",
4434
4453
  fallbackCommands: [script],
4454
+ commandTimeoutMs: 120_000,
4435
4455
  }),
4436
4456
  cwd: ".",
4437
4457
  timeoutMs: 120000,
@@ -4526,6 +4546,7 @@ function buildKgBootstrapValidateNode(sources) {
4526
4546
  commandSource: "inline",
4527
4547
  fallbackCommands: [script],
4528
4548
  finalFullRequired: true,
4549
+ commandTimeoutMs: 120_000,
4529
4550
  }),
4530
4551
  cwd: ".",
4531
4552
  timeoutMs: 120000,
@@ -4653,6 +4674,7 @@ function buildKgBootstrapPromoteNode(sources) {
4653
4674
  commandSource: "inline",
4654
4675
  fallbackCommands: [script],
4655
4676
  finalFullRequired: true,
4677
+ commandTimeoutMs: 120_000,
4656
4678
  }),
4657
4679
  cwd: ".",
4658
4680
  timeoutMs: 120000,
@@ -4718,6 +4740,7 @@ function buildKgBootstrapMaterializeNode(sources) {
4718
4740
  commandSource: "inline",
4719
4741
  fallbackCommands: [script],
4720
4742
  finalFullRequired: true,
4743
+ commandTimeoutMs: 120_000,
4721
4744
  }),
4722
4745
  cwd: ".",
4723
4746
  timeoutMs: 120000,
@@ -4774,7 +4797,22 @@ export async function requireManagedTaskContractBinding(input) {
4774
4797
  taskId: input.taskId,
4775
4798
  });
4776
4799
  if (state.effectiveStatus !== "managed" || !state.ref) {
4777
- const err = new Error(`dag generate requires managed Task Contract for task ${input.taskId} (effectiveStatus=${state.effectiveStatus}); run: loop-agent task contract adopt|apply --task ${input.taskId} ...`);
4800
+ const expectedRevision = state.ref?.revision ?? 0;
4801
+ const observedCanonicalHash = state.observedCanonicalHash ?? "<observedCanonicalHash-from-show>";
4802
+ const recovery = state.effectiveStatus === "transaction-incomplete"
4803
+ ? `Recover the unfinished transaction first: loop-agent task contract recover --task ${input.taskId} --json`
4804
+ : [
4805
+ "After reviewing the show JSON, either adopt the current on-disk facts:",
4806
+ `loop-agent task contract adopt --task ${input.taskId} --expected-revision ${expectedRevision} --expected-observed-hash ${observedCanonicalHash} --request-id <unique-request-id> --request-payload-sha256 <sha256-of-this-adopt-request> --json`,
4807
+ "or apply a reviewed draft:",
4808
+ `loop-agent task contract apply --task ${input.taskId} --input <reviewed-draft.json> --expected-revision ${expectedRevision} --expected-observed-hash ${observedCanonicalHash} --request-id <unique-request-id> --request-payload-sha256 <sha256-of-input-file> --json`,
4809
+ ].join(" ");
4810
+ const err = new Error([
4811
+ `dag generate requires managed Task Contract for task ${input.taskId} (effectiveStatus=${state.effectiveStatus})`,
4812
+ `Inspect current state: loop-agent task contract show ${input.taskId} --json`,
4813
+ recovery,
4814
+ "Do not auto-adopt or auto-apply before confirming the Task Contract facts and concurrency fields.",
4815
+ ].join("; "));
4778
4816
  err.code =
4779
4817
  state.effectiveStatus === "externally-modified"
4780
4818
  ? "EXTERNALLY_MODIFIED"
@@ -4828,10 +4866,6 @@ function synthesizeInMemoryTaskContractBinding(sources) {
4828
4866
  };
4829
4867
  }
4830
4868
  async function resolveTaskContractBindingForGenerate(sources) {
4831
- // Real CLI generation freezes the managed ref. Direct builder unit tests
4832
- // may provide repoRoot-shaped fixture paths without a persisted task; keep
4833
- // their deterministic synthetic binding path so topology/prompt tests remain
4834
- // isolated from contract storage.
4835
4869
  if (sources.repoRoot) {
4836
4870
  const state = await observeTaskContract({
4837
4871
  repoRoot: sources.repoRoot,
@@ -4986,7 +5020,12 @@ function buildReviewVerdictRecoveryNode(sources) {
4986
5020
  return {
4987
5021
  id: "review-verdict-recovery-pi",
4988
5022
  depends_on: ["review-pi"],
5023
+ // AC3: recovery is a read-only failure-aware node. It runs after review-pi
5024
+ // settles, consuming either normal FINISHED output or an explicitly tolerated
5025
+ // ERROR (for example protocol-invalid after retries). It must not bypass the
5026
+ // writer, write guard, or final shell gate, which remain fail-closed.
4989
5027
  role: "reviewer",
5028
+ failureAwareDependsOn: ["review-pi"],
4990
5029
  executor: "pi",
4991
5030
  complexity: "LOW",
4992
5031
  writePolicy: "read-only",
@@ -5271,7 +5310,7 @@ function buildWriteSetGateNode(sources) {
5271
5310
  }
5272
5311
  function buildSoftVerifyNode(sources) {
5273
5312
  const implementId = implementationNodeId();
5274
- const strategy = resolveDagVerifyStrategy(sources.taskConfig);
5313
+ const strategy = resolveDagVerifyStrategy(sources.taskConfig, "1");
5275
5314
  const fallbackCommands = ["npm run typecheck"];
5276
5315
  const commands = buildVerifyShellCommands({
5277
5316
  repoRoot: sources.repoRoot,
@@ -5297,9 +5336,10 @@ function buildSoftVerifyNode(sources) {
5297
5336
  commandSource: sources.verifyCommands ? "adapter" : "inline",
5298
5337
  commands: sources.verifyCommands?.intermediate,
5299
5338
  fallbackCommands,
5339
+ commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
5300
5340
  }),
5301
5341
  cwd: ".",
5302
- timeoutMs: 300000,
5342
+ timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
5303
5343
  },
5304
5344
  };
5305
5345
  }
@@ -5309,6 +5349,7 @@ function buildProcessSupervisorNode(sources) {
5309
5349
  return {
5310
5350
  id: "process-supervisor-pi",
5311
5351
  depends_on: ["soft-verify-shell", implementId],
5352
+ failureAwareDependsOn: ["soft-verify-shell"],
5312
5353
  role: "supervisor",
5313
5354
  executor: "pi",
5314
5355
  complexity: "HIGH",
@@ -5415,9 +5456,10 @@ function buildHardVerifyNode(sources) {
5415
5456
  commands: sources.verifyCommands?.final,
5416
5457
  fallbackCommands,
5417
5458
  finalFullRequired: true,
5459
+ commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
5418
5460
  }),
5419
5461
  cwd: ".",
5420
- timeoutMs: 300000,
5462
+ timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
5421
5463
  },
5422
5464
  };
5423
5465
  }
@@ -5466,7 +5508,7 @@ function buildSupervisedHybridDag(standard, sources) {
5466
5508
  objective: `${standard.objective ?? ""}\n\nRoute: supervised implementation DAG selected by workflowPolicy/governanceProfile or explicit CLI profile.`.trim(),
5467
5509
  globalConstraints: supervisedConstraints,
5468
5510
  convergence: sources.taskConfig.convergence,
5469
- verifyStrategy: resolveDagVerifyStrategy(sources.taskConfig),
5511
+ verifyStrategy: resolveDagVerifyStrategy(sources.taskConfig, "1"),
5470
5512
  tasks: [
5471
5513
  cloneTask(contract),
5472
5514
  cloneTask(scoutSrc),
@@ -5536,6 +5578,12 @@ export async function writeHybridDagDraft(sources, outputPath, options = {}) {
5536
5578
  ? await prepareFrontendMockSources(sources)
5537
5579
  : sources;
5538
5580
  const spec = await buildHybridDagForTemplate(preparedSources, template);
5581
+ if (preparedSources.repoRoot && dagHasWriterExecution(spec)) {
5582
+ await requireManagedTaskContractBinding({
5583
+ repoRoot: preparedSources.repoRoot,
5584
+ taskId: preparedSources.taskId,
5585
+ });
5586
+ }
5539
5587
  await writeFile(outputPath, `${JSON.stringify(spec, null, 2)}\n`, "utf-8");
5540
5588
  return {
5541
5589
  taskId: sources.taskId,
@@ -9,7 +9,7 @@ import { buildDagNodePromptEnvelope } from "./prompt.js";
9
9
  import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
10
10
  import { computeBackoffDelayMs, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, } from "./retry-policy.js";
11
11
  import { applyNodeActivity, evaluateNodeLiveness, resolveLivenessPolicy, } from "./liveness-policy.js";
12
- import { buildProtocolRetryInstruction, validateOutputProtocol, } from "./output-protocol.js";
12
+ import { buildProtocolRetryInstruction, normalizeReviewVerdictAfterRetries, validateOutputProtocol, } from "./output-protocol.js";
13
13
  import { writeDagNodeJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
14
14
  import { buildProjectGovernanceContext, readCompletedWriterChangeManifests, writeProjectGovernanceContext, } from "./project-governance-context.js";
15
15
  import { assertSkillSnapshotCoversSpec, buildNodePromptFromSnapshot, isDagSkillSnapshotIntegrityError, readSkillSnapshot, } from "./skill-snapshot.js";
@@ -483,6 +483,43 @@ export async function executeDagNode(input) {
483
483
  await input.persistState();
484
484
  }
485
485
  }
486
+ // AC2: after retries are exhausted on a canonical review verdict protocol
487
+ // node, attempt one deterministic normalization of the full terminal output.
488
+ // This ONLY re-wraps output so the first non-empty line is the single
489
+ // supported verdict; it never changes the verdict value, and missing /
490
+ // duplicated / conflicting / illegal verdicts remain protocol-invalid.
491
+ // Shell, write-guard, timeout, and model failures are never normalized.
492
+ if (terminalResult &&
493
+ !terminalResult.ok &&
494
+ terminalResult.failureCategory === "protocol-invalid" &&
495
+ task.outputProtocol) {
496
+ const fullText = terminalResult.assistantText && terminalResult.assistantText.trim().length > 0
497
+ ? terminalResult.assistantText
498
+ : terminalResult.stdout ?? "";
499
+ const normalized = normalizeReviewVerdictAfterRetries(task.outputProtocol, fullText);
500
+ if (normalized.ok) {
501
+ const normalizedText = normalized.normalizedOutput;
502
+ terminalResult = {
503
+ ...terminalResult,
504
+ ok: true,
505
+ failureCategory: undefined,
506
+ assistantText: normalizedText,
507
+ stdout: normalizedText,
508
+ stderr: [
509
+ terminalResult.stderr,
510
+ `verdict normalized after retries exhausted: ${normalized.verdictLine}`,
511
+ ]
512
+ .filter(Boolean)
513
+ .join("\n"),
514
+ };
515
+ // Sync the observable node fields so downstream gates and reports see the
516
+ // normalized first-line verdict while the original evidence is preserved.
517
+ node.assistantText = normalizedText;
518
+ node.stdout = normalizedText;
519
+ node.failureCategory = undefined;
520
+ node.stderr = terminalResult.stderr;
521
+ }
522
+ }
486
523
  const result = terminalResult;
487
524
  const started = Date.now();
488
525
  try {
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { normalizeVerdictCandidateLine } from "./dynamic-runtime/shared.js";
2
3
  /**
3
4
  * Machine-readable output protocol for safe read-only Pi nodes.
4
5
  *
@@ -74,3 +75,91 @@ export function buildProtocolRetryInstruction(protocol, reason) {
74
75
  "</retry_instruction>",
75
76
  ].join("\n");
76
77
  }
78
+ /**
79
+ * AC2: deterministic review-verdict normalization applied ONLY after retries
80
+ * are exhausted on a review verdict protocol node.
81
+ *
82
+ * The canonical review verdict protocol accepts exactly one of
83
+ * {@link REVIEW_VERDICT_OUTPUT_PROTOCOL}.validLines. This helper scans the
84
+ * FULL output (assistantText + stdout style text) for verdict lines using the
85
+ * same whole-line Markdown-emphasis normalization the verdict gate uses.
86
+ *
87
+ * Normalization succeeds if and only if the full output contains exactly one
88
+ * supported, unambiguous verdict line. It produces a normalized output whose
89
+ * first non-empty line is that verdict, preserving the original evidence text
90
+ * afterwards. It never changes the verdict value.
91
+ *
92
+ * It fails (returns ok:false) for:
93
+ * - no verdict line anywhere;
94
+ * - multiple verdict lines, even if identical (duplicated protocol ambiguity);
95
+ * - conflicting verdict lines;
96
+ * - any unsupported/illegal verdict value (e.g. `VERDICT: approve`);
97
+ * - malformed lines that are not a supported verdict after normalization;
98
+ * - natural-language approval prose without a real verdict token.
99
+ *
100
+ * This helper only operates on the canonical review verdict protocol; any
101
+ * other protocol is left untouched (ok:false) so decision envelopes, shell
102
+ * output, and structured protocols are never loosely tolerated.
103
+ */
104
+ export function normalizeReviewVerdictAfterRetries(protocol, text) {
105
+ const isReviewVerdictProtocol = protocol.type === REVIEW_VERDICT_OUTPUT_PROTOCOL.type &&
106
+ Array.from(protocol.validLines).every((line, index) => line === REVIEW_VERDICT_OUTPUT_PROTOCOL.validLines[index]) &&
107
+ protocol.validLines.length ===
108
+ REVIEW_VERDICT_OUTPUT_PROTOCOL.validLines.length;
109
+ if (!isReviewVerdictProtocol) {
110
+ return {
111
+ ok: false,
112
+ reason: "deterministic verdict normalization only applies to the canonical review verdict protocol",
113
+ };
114
+ }
115
+ const supported = new Set(REVIEW_VERDICT_OUTPUT_PROTOCOL.validLines);
116
+ const candidates = [];
117
+ let seenIllegalVerdictPrefixedLine = false;
118
+ for (const rawLine of String(text).split(/\r?\n/)) {
119
+ const normalized = normalizeVerdictCandidateLine(rawLine);
120
+ if (!normalized)
121
+ continue;
122
+ // Detect any line that looks like a verdict token (VERDICT: ...) but is
123
+ // not a supported value, so a single valid + single illegal line cannot
124
+ // be silently normalized to the valid one.
125
+ if (/^VERDICT:/.test(normalized) && !supported.has(normalized)) {
126
+ seenIllegalVerdictPrefixedLine = true;
127
+ continue;
128
+ }
129
+ if (supported.has(normalized)) {
130
+ candidates.push(normalized);
131
+ }
132
+ }
133
+ if (seenIllegalVerdictPrefixedLine) {
134
+ return {
135
+ ok: false,
136
+ reason: "output contains an illegal or unsupported VERDICT value; cannot normalize",
137
+ };
138
+ }
139
+ if (candidates.length === 0) {
140
+ return {
141
+ ok: false,
142
+ reason: "no supported verdict line found in full output; cannot normalize",
143
+ };
144
+ }
145
+ const unique = new Set(candidates);
146
+ if (unique.size !== 1 || candidates.length !== 1) {
147
+ return {
148
+ ok: false,
149
+ reason: "output contains multiple or conflicting verdict lines; normalization requires exactly one supported verdict",
150
+ };
151
+ }
152
+ const verdictLine = candidates[0];
153
+ const preservedEvidence = String(text)
154
+ .split(/\r?\n/)
155
+ .filter((line) => {
156
+ const normalized = normalizeVerdictCandidateLine(line);
157
+ return !normalized || !supported.has(normalized);
158
+ })
159
+ .join("\n")
160
+ .trim();
161
+ const normalizedOutput = preservedEvidence
162
+ ? `${verdictLine}\n${preservedEvidence}`
163
+ : verdictLine;
164
+ return { ok: true, verdictLine, normalizedOutput };
165
+ }
@@ -58,13 +58,47 @@ export function formatOutputLanguageBlock(language = DEFAULT_DAG_OUTPUT_LANGUAGE
58
58
  }
59
59
  export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHARS) {
60
60
  const sections = [];
61
+ const toleratedErrors = new Set(task.failureAwareDependsOn ?? []);
61
62
  for (const depId of task.depends_on) {
62
63
  const record = upstream[depId];
63
64
  const stdout = record?.stdout?.trim() ? record.stdout : "";
64
65
  const assistantText = record?.assistantText?.trim()
65
66
  ? record.assistantText
66
67
  : "";
67
- const upstreamText = stdout || assistantText;
68
+ const stderr = record?.stderr?.trim() ? record.stderr : "";
69
+ const upstreamText = stdout || assistantText || stderr;
70
+ // AC1: a failure-aware recovery/diagnosis/closeout node may consume an
71
+ // upstream ERROR as settled failure evidence. Surface that evidence
72
+ // explicitly so the node can read the failure without the upstream being
73
+ // rewritten to FINISHED.
74
+ if (record?.status === "ERROR" &&
75
+ toleratedErrors.has(depId) &&
76
+ upstreamText) {
77
+ const { preview, truncated } = formatStdoutPreview(upstreamText, maxChars);
78
+ let section = `## Upstream failure evidence: ${depId} (status=ERROR${record.failureCategory ? `, failureCategory=${record.failureCategory}` : ""})\n${preview}`;
79
+ const artifactKind = stdout
80
+ ? "stdout"
81
+ : assistantText
82
+ ? "assistant"
83
+ : "stderr";
84
+ const artifactPath = artifactKind === "stdout"
85
+ ? record.stdoutArtifactPath
86
+ : artifactKind === "assistant"
87
+ ? record.assistantArtifactPath
88
+ : undefined;
89
+ if (truncated && artifactPath) {
90
+ section += formatUpstreamArtifactPointerMap(record, artifactKind === "stdout" ? "stdout" : "assistant");
91
+ }
92
+ else if (truncated && record.nodeRecordPath) {
93
+ section += [
94
+ "",
95
+ "Full failure record (read-only runner evidence — use read tool to fetch; do not edit):",
96
+ `- node record: ${record.nodeRecordPath}`,
97
+ ].join("\n");
98
+ }
99
+ sections.push(section);
100
+ continue;
101
+ }
68
102
  if (!record || record.status !== "FINISHED" || !upstreamText)
69
103
  continue;
70
104
  const artifactKind = stdout ? "stdout" : "assistant";