@tea-agent/loop-agent 0.20.1-beta.0 → 0.21.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.
- package/CHANGELOG.md +50 -0
- package/dist/application/dag/args.js +29 -0
- package/dist/application/dag/run-dag.js +3 -1
- package/dist/cli/command-definitions.js +15 -1
- package/dist/cli/program.js +11 -1
- package/dist/commands/dag-rerun-task.js +19 -0
- package/dist/commands/dag-rerun.js +111 -0
- package/dist/commands/init.js +7 -0
- package/dist/executors/dag-pi-executor.js +24 -0
- package/dist/executors/pi-executor.js +111 -36
- package/dist/executors/pi-sdk-executor.js +105 -29
- package/dist/executors/shell-executor.js +54 -11
- package/dist/shared/operator/capabilities.js +54 -0
- package/dist/worker/console/index.js +1 -1
- package/dist/worker/console/inspect-split.js +82 -0
- package/dist/worker/console/operation-runner.js +3 -1
- package/dist/worker/console/operation-store.js +1 -0
- package/dist/worker/console/operator-actions.js +153 -2
- package/dist/worker/console/operator-user-error.js +10 -0
- package/dist/worker/console/pi-readiness.js +4 -0
- package/dist/worker/console/recovery-cta.js +116 -5
- package/dist/worker/console/recovery-selection.js +107 -0
- package/dist/worker/console/resolve-dag-run-for-task.js +50 -11
- package/dist/worker/console/routes.js +20 -0
- package/dist/worker/console/sibling-controller.js +12 -7
- package/dist/worker/console/static/assets/index-CUDke82y.js +18 -0
- package/dist/worker/console/static/assets/index-wSEksVSO.css +1 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/loop-agent/loop-agent-client.js +43 -9
- package/dist/worker/observability/read-model.js +67 -1
- package/dist/worker/observe/static/constants.js +5 -0
- package/dist/worker/observe/static/format-pool.js +22 -3
- package/dist/worker/observe/static/index.html +1 -1
- package/dist/worker/observe/static/styles.css +32 -3
- package/dist/worker/observe/static/views/dag-inspector.js +2 -2
- package/dist/worker/run-task/run-task.js +23 -6
- package/dist/workflows/dag/backend-test-markdown-workflow.js +291 -97
- package/dist/workflows/dag/backend-test-result-contract.js +10 -4
- package/dist/workflows/dag/frontend-test-result-contract.js +64 -0
- package/dist/workflows/dag/init-hybrid.js +117 -64
- package/dist/workflows/dag/lifecycle.js +60 -4
- package/dist/workflows/dag/liveness-policy.js +250 -0
- package/dist/workflows/dag/node-execution.js +89 -6
- package/dist/workflows/dag/output-protocol.js +76 -0
- package/dist/workflows/dag/rerun-plan.js +611 -0
- package/dist/workflows/dag/rerun-run.js +497 -0
- package/dist/workflows/dag/rerun-task.js +284 -0
- package/dist/workflows/dag/retry-policy.js +20 -1
- package/dist/workflows/dag/runner.js +71 -1
- package/dist/workflows/dag/skill-snapshot.js +22 -3
- package/dist/workflows/dag/types.js +12 -0
- package/dist/workflows/dag/validate.js +11 -0
- package/dist/workflows/dag/workspace-checkpoint.js +163 -0
- package/docs/README.md +5 -5
- package/docs/architecture/dag-execution.md +11 -0
- package/docs/architecture/facts-and-state.md +1 -0
- package/docs/architecture/worker-and-feature.md +10 -0
- package/docs/templates/agent-dag.schema.json +17 -2
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +5 -2
- package/docs/templates/backend-test-dag.json +15 -15
- package/docs/templates/frontend-test-case-checklist.md +16 -1
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +26 -2
- package/docs/templates/frontend-test-dag.json +65 -6
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +4 -1
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +5 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/skills/playwright-cli-case-generator/SKILL.md +35 -7
- package/dist/worker/console/static/assets/index-3vsjZJHq.js +0 -16
- package/dist/worker/console/static/assets/index-i1wV4LrY.css +0 -1
|
@@ -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))}}@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-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-CUDke82y.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-wSEksVSO.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
|
@@ -206,11 +206,12 @@ export class LoopAgentClient {
|
|
|
206
206
|
const stdoutPath = path.join(artifactDir, "stdout.txt");
|
|
207
207
|
const stderrPath = path.join(artifactDir, "stderr.txt");
|
|
208
208
|
const resultPath = path.join(artifactDir, "result.json");
|
|
209
|
+
const configuredTimeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;
|
|
209
210
|
const { stdout, stdoutBytes, stdoutTruncated, stderr, stderrBytes, stderrTruncated, exitCode, timedOut, } = await spawnCommand({
|
|
210
211
|
command,
|
|
211
212
|
args: commandArgs,
|
|
212
213
|
cwd: options.cwd,
|
|
213
|
-
|
|
214
|
+
hardTimeoutMs: configuredTimeoutMs > 0 ? configuredTimeoutMs : undefined,
|
|
214
215
|
env: { ...process.env, ...this.env, ...options.env },
|
|
215
216
|
stdoutPath, stderrPath, resultPath,
|
|
216
217
|
heartbeatIntervalMs: this.heartbeatIntervalMs,
|
|
@@ -362,23 +363,56 @@ function spawnCommand(input) {
|
|
|
362
363
|
let stdout = createBoundedOutput();
|
|
363
364
|
let stderr = createBoundedOutput();
|
|
364
365
|
let timedOut = false;
|
|
365
|
-
|
|
366
|
+
let settled = false;
|
|
367
|
+
const settle = (payload) => {
|
|
368
|
+
if (settled)
|
|
369
|
+
return;
|
|
370
|
+
settled = true;
|
|
371
|
+
clearTimers();
|
|
372
|
+
resolve({
|
|
373
|
+
stdout: formatBoundedOutput(stdout),
|
|
374
|
+
stdoutBytes: stdout.bytes,
|
|
375
|
+
stdoutTruncated: stdout.truncated,
|
|
376
|
+
stderr: formatBoundedOutput(stderr),
|
|
377
|
+
stderrBytes: stderr.bytes,
|
|
378
|
+
stderrTruncated: stderr.truncated,
|
|
379
|
+
exitCode: payload.exitCode,
|
|
380
|
+
timedOut: payload.timedOut,
|
|
381
|
+
});
|
|
382
|
+
};
|
|
383
|
+
let hardTimeout;
|
|
384
|
+
if (input.hardTimeoutMs !== undefined && input.hardTimeoutMs > 0) {
|
|
385
|
+
hardTimeout = setTimeout(() => {
|
|
386
|
+
timedOut = true;
|
|
387
|
+
child.kill("SIGTERM");
|
|
388
|
+
}, input.hardTimeoutMs);
|
|
389
|
+
}
|
|
366
390
|
let heartbeatTimer;
|
|
367
391
|
if (input.heartbeatIntervalMs > 0) {
|
|
368
392
|
heartbeatTimer = setInterval(() => { invokeHeartbeatCallback(input.onHeartbeat, startedAtMs); }, input.heartbeatIntervalMs);
|
|
369
393
|
}
|
|
370
|
-
const clearTimers = () => {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
394
|
+
const clearTimers = () => {
|
|
395
|
+
if (hardTimeout !== undefined)
|
|
396
|
+
clearTimeout(hardTimeout);
|
|
397
|
+
if (heartbeatTimer !== undefined) {
|
|
398
|
+
clearInterval(heartbeatTimer);
|
|
399
|
+
heartbeatTimer = undefined;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
374
402
|
child.stdout.setEncoding("utf8");
|
|
375
403
|
child.stderr.setEncoding("utf8");
|
|
376
404
|
child.stdout.on("data", (chunk) => { stdout = appendBoundedOutput(stdout, chunk); appendChunkBestEffort(input.stdoutPath, chunk); invokeChunkCallback(input.onStdout, chunk); });
|
|
377
405
|
child.stderr.on("data", (chunk) => { stderr = appendBoundedOutput(stderr, chunk); appendChunkBestEffort(input.stderrPath, chunk); invokeChunkCallback(input.onStderr, chunk); });
|
|
378
|
-
child.on("error", (error) => {
|
|
379
|
-
|
|
406
|
+
child.on("error", (error) => { if (!settled) {
|
|
407
|
+
settled = true;
|
|
380
408
|
clearTimers();
|
|
381
|
-
|
|
409
|
+
reject(error);
|
|
410
|
+
} });
|
|
411
|
+
child.on("close", (exitCode) => {
|
|
412
|
+
settle({
|
|
413
|
+
exitCode,
|
|
414
|
+
timedOut,
|
|
415
|
+
});
|
|
382
416
|
});
|
|
383
417
|
});
|
|
384
418
|
}
|
|
@@ -717,6 +717,8 @@ function computeHealth(batches, tasks, dagRuns) {
|
|
|
717
717
|
const ACTIVE_EFFECTIVE_STATUSES = new Set([
|
|
718
718
|
"running",
|
|
719
719
|
"running-quiet",
|
|
720
|
+
"running-suspected-stall",
|
|
721
|
+
"needs-attention",
|
|
720
722
|
"remote-unknown",
|
|
721
723
|
"pending",
|
|
722
724
|
]);
|
|
@@ -796,8 +798,12 @@ function computeDagHealth(dagRuns) {
|
|
|
796
798
|
pausedRuns++;
|
|
797
799
|
if (dag.effectiveStatus === "failed")
|
|
798
800
|
failedRuns++;
|
|
799
|
-
if (liveness === "stale"
|
|
801
|
+
if (liveness === "stale"
|
|
802
|
+
|| liveness === "orphaned"
|
|
803
|
+
|| liveness === "suspected-stall"
|
|
804
|
+
|| liveness === "needs-attention") {
|
|
800
805
|
staleRuns++;
|
|
806
|
+
}
|
|
801
807
|
if (dag.effectiveStatus === "interrupted")
|
|
802
808
|
interruptedRuns++;
|
|
803
809
|
if (dag.stateConsistent === false)
|
|
@@ -1574,6 +1580,39 @@ async function parseDagStateFile(statePath, now) {
|
|
|
1574
1580
|
})
|
|
1575
1581
|
: undefined;
|
|
1576
1582
|
const backendTest = await loadBackendTestProjection(runDir, nodes);
|
|
1583
|
+
const continuationRecord = state.continuation &&
|
|
1584
|
+
typeof state.continuation.parentRunId === "string" &&
|
|
1585
|
+
typeof state.continuation.effectiveFromNodeId === "string" &&
|
|
1586
|
+
typeof state.continuation.operatorSelectedNodeId === "string"
|
|
1587
|
+
? state.continuation
|
|
1588
|
+
: undefined;
|
|
1589
|
+
const rerunPlan = continuationRecord
|
|
1590
|
+
? await safeReadJson(path.join(runDir, ".runtime", "rerun-plan.json"))
|
|
1591
|
+
: undefined;
|
|
1592
|
+
const verification = rerunPlan && typeof rerunPlan.verificationPolicy === "object"
|
|
1593
|
+
? rerunPlan.verificationPolicy
|
|
1594
|
+
: undefined;
|
|
1595
|
+
const continuation = continuationRecord
|
|
1596
|
+
? {
|
|
1597
|
+
parentRunId: continuationRecord.parentRunId,
|
|
1598
|
+
effectiveFromNodeId: continuationRecord.effectiveFromNodeId,
|
|
1599
|
+
operatorSelectedNodeId: continuationRecord.operatorSelectedNodeId,
|
|
1600
|
+
...(verification &&
|
|
1601
|
+
(verification.mode === "inherited" || verification.mode === "rerun") &&
|
|
1602
|
+
Array.isArray(verification.nodeIds) &&
|
|
1603
|
+
verification.nodeIds.every((nodeId) => typeof nodeId === "string")
|
|
1604
|
+
? {
|
|
1605
|
+
verificationPolicy: {
|
|
1606
|
+
mode: verification.mode,
|
|
1607
|
+
nodeIds: verification.nodeIds,
|
|
1608
|
+
...(typeof verification.inheritedFromRunId === "string"
|
|
1609
|
+
? { inheritedFromRunId: verification.inheritedFromRunId }
|
|
1610
|
+
: {}),
|
|
1611
|
+
},
|
|
1612
|
+
}
|
|
1613
|
+
: {}),
|
|
1614
|
+
}
|
|
1615
|
+
: undefined;
|
|
1577
1616
|
return {
|
|
1578
1617
|
dagRunId,
|
|
1579
1618
|
status,
|
|
@@ -1590,6 +1629,7 @@ async function parseDagStateFile(statePath, now) {
|
|
|
1590
1629
|
nodes,
|
|
1591
1630
|
edges,
|
|
1592
1631
|
dagPath: runDir,
|
|
1632
|
+
...(continuation ? { continuation } : {}),
|
|
1593
1633
|
...(backendTest ? { backendTest } : {}),
|
|
1594
1634
|
};
|
|
1595
1635
|
}
|
|
@@ -1699,6 +1739,10 @@ function parseDagNodes(parsed, rankByNode, modelByNode) {
|
|
|
1699
1739
|
const nodeStatus = readString(node, "status");
|
|
1700
1740
|
const outputPreview = nodeOutputPreview(node);
|
|
1701
1741
|
const errorPreview = nodeErrorPreview(node, nodeStatus);
|
|
1742
|
+
const attempts = Array.isArray(node.attempts) ? node.attempts : undefined;
|
|
1743
|
+
const origin = node.origin && typeof node.origin === "object" && !Array.isArray(node.origin)
|
|
1744
|
+
? node.origin
|
|
1745
|
+
: undefined;
|
|
1702
1746
|
nodes.push({
|
|
1703
1747
|
nodeId: resolvedId,
|
|
1704
1748
|
rank: rankByNode.get(nodeId) ?? rankByNode.get(resolvedId),
|
|
@@ -1711,6 +1755,15 @@ function parseDagNodes(parsed, rankByNode, modelByNode) {
|
|
|
1711
1755
|
finishedAt: readString(node, "finishedAt"),
|
|
1712
1756
|
...(outputPreview ? { outputPreview } : {}),
|
|
1713
1757
|
...(errorPreview ? { errorPreview } : {}),
|
|
1758
|
+
...(attempts && attempts.length > 0
|
|
1759
|
+
? { attemptCount: attempts.length }
|
|
1760
|
+
: {}),
|
|
1761
|
+
...(readString(node, "failureCategory")
|
|
1762
|
+
? { failureCategory: readString(node, "failureCategory") }
|
|
1763
|
+
: {}),
|
|
1764
|
+
...(origin?.kind === "imported" || origin?.kind === "executed"
|
|
1765
|
+
? { originKind: origin.kind }
|
|
1766
|
+
: {}),
|
|
1714
1767
|
});
|
|
1715
1768
|
}
|
|
1716
1769
|
return nodes.sort((a, b) => a.nodeId.localeCompare(b.nodeId));
|
|
@@ -1721,6 +1774,10 @@ function parseDagNodes(parsed, rankByNode, modelByNode) {
|
|
|
1721
1774
|
const nodeStatus = readString(node, "status");
|
|
1722
1775
|
const outputPreview = nodeOutputPreview(node);
|
|
1723
1776
|
const errorPreview = nodeErrorPreview(node, nodeStatus);
|
|
1777
|
+
const attempts = Array.isArray(node.attempts) ? node.attempts : undefined;
|
|
1778
|
+
const origin = node.origin && typeof node.origin === "object" && !Array.isArray(node.origin)
|
|
1779
|
+
? node.origin
|
|
1780
|
+
: undefined;
|
|
1724
1781
|
nodes.push({
|
|
1725
1782
|
nodeId,
|
|
1726
1783
|
rank: rankByNode.get(nodeId),
|
|
@@ -1733,6 +1790,15 @@ function parseDagNodes(parsed, rankByNode, modelByNode) {
|
|
|
1733
1790
|
finishedAt: readString(node, "finishedAt"),
|
|
1734
1791
|
...(outputPreview ? { outputPreview } : {}),
|
|
1735
1792
|
...(errorPreview ? { errorPreview } : {}),
|
|
1793
|
+
...(attempts && attempts.length > 0
|
|
1794
|
+
? { attemptCount: attempts.length }
|
|
1795
|
+
: {}),
|
|
1796
|
+
...(readString(node, "failureCategory")
|
|
1797
|
+
? { failureCategory: readString(node, "failureCategory") }
|
|
1798
|
+
: {}),
|
|
1799
|
+
...(origin?.kind === "imported" || origin?.kind === "executed"
|
|
1800
|
+
? { originKind: origin.kind }
|
|
1801
|
+
: {}),
|
|
1736
1802
|
});
|
|
1737
1803
|
}
|
|
1738
1804
|
return nodes.sort((a, b) => a.nodeId.localeCompare(b.nodeId));
|
|
@@ -75,6 +75,9 @@ export const LIVENESS_LABELS = {
|
|
|
75
75
|
active: "执行器正常",
|
|
76
76
|
"node-quiet": "执行器正常,节点长时间无活动",
|
|
77
77
|
quiet: "无输出(存活)",
|
|
78
|
+
"suspected-stall": "疑似卡死",
|
|
79
|
+
"needs-attention": "需人工关注",
|
|
80
|
+
"needs-reconcile": "需对账",
|
|
78
81
|
stale: "执行器心跳中断",
|
|
79
82
|
"timeout-risk": "即将超时",
|
|
80
83
|
orphaned: "执行器已退出",
|
|
@@ -86,6 +89,8 @@ export const DAG_EFFECTIVE_STATUS_LABELS = {
|
|
|
86
89
|
pending: "等待执行",
|
|
87
90
|
running: "正在执行",
|
|
88
91
|
"running-quiet": "运行可疑",
|
|
92
|
+
"running-suspected-stall": "疑似卡死",
|
|
93
|
+
"needs-attention": "需人工关注",
|
|
89
94
|
paused: "已暂停",
|
|
90
95
|
interrupted: "执行已中断",
|
|
91
96
|
"remote-unknown": "远端状态未知",
|
|
@@ -71,7 +71,18 @@ export function isPoolAttentionTask(task) {
|
|
|
71
71
|
const st = (task?.status ?? "").toLowerCase();
|
|
72
72
|
const live = (task?.liveness ?? "").toLowerCase();
|
|
73
73
|
if (["failed", "error", "blocked", "stale"].includes(st)) return true;
|
|
74
|
-
if (
|
|
74
|
+
if (
|
|
75
|
+
[
|
|
76
|
+
"stale",
|
|
77
|
+
"orphaned",
|
|
78
|
+
"timeout-risk",
|
|
79
|
+
"suspected-stall",
|
|
80
|
+
"needs-attention",
|
|
81
|
+
"needs-reconcile",
|
|
82
|
+
].includes(live)
|
|
83
|
+
) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
75
86
|
return false;
|
|
76
87
|
}
|
|
77
88
|
|
|
@@ -79,8 +90,16 @@ export function attentionRank(task) {
|
|
|
79
90
|
const st = (task?.status ?? "").toLowerCase();
|
|
80
91
|
const live = (task?.liveness ?? "").toLowerCase();
|
|
81
92
|
if (st === "failed" || st === "error") return 0;
|
|
82
|
-
if (live === "timeout-risk") return 1;
|
|
83
|
-
if (
|
|
93
|
+
if (live === "timeout-risk" || live === "needs-attention") return 1;
|
|
94
|
+
if (
|
|
95
|
+
st === "stale"
|
|
96
|
+
|| live === "stale"
|
|
97
|
+
|| live === "orphaned"
|
|
98
|
+
|| live === "suspected-stall"
|
|
99
|
+
|| live === "needs-reconcile"
|
|
100
|
+
) {
|
|
101
|
+
return 2;
|
|
102
|
+
}
|
|
84
103
|
if (st === "blocked") return 3;
|
|
85
104
|
return 9;
|
|
86
105
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<body data-shell="observe-console">
|
|
16
16
|
<a class="skip-link" href="#main-content">跳到主内容</a>
|
|
17
17
|
<nav class="embedded-operator-nav" aria-label="Operator 导航">
|
|
18
|
-
<a href="/"
|
|
18
|
+
<a href="/">返回操作台</a>
|
|
19
19
|
<span aria-current="page">观测</span>
|
|
20
20
|
</nav>
|
|
21
21
|
<div id="app">
|
|
@@ -845,10 +845,14 @@ td:first-child,
|
|
|
845
845
|
.kpi-tip {
|
|
846
846
|
position: absolute;
|
|
847
847
|
left: 12px;
|
|
848
|
-
right:
|
|
848
|
+
right: auto;
|
|
849
849
|
top: calc(100% - 8px);
|
|
850
850
|
z-index: 10;
|
|
851
|
-
|
|
851
|
+
/* Stable readable width independent of narrow KPI cards; viewport-safe cap. */
|
|
852
|
+
width: min(280px, calc(100vw - 24px));
|
|
853
|
+
min-width: min(240px, calc(100vw - 24px));
|
|
854
|
+
max-width: min(280px, calc(100vw - 24px));
|
|
855
|
+
box-sizing: border-box;
|
|
852
856
|
padding: 8px 10px;
|
|
853
857
|
border: 1px solid var(--hairline-strong);
|
|
854
858
|
border-radius: var(--radius-md);
|
|
@@ -863,6 +867,12 @@ td:first-child,
|
|
|
863
867
|
pointer-events: none;
|
|
864
868
|
transition: opacity var(--transition-fast);
|
|
865
869
|
}
|
|
870
|
+
/* Five-column default: right two columns open inward (left) to avoid viewport overflow. */
|
|
871
|
+
.kpi-grid:not(.pool-health-grid) > .kpi-card:nth-child(5n-1) .kpi-tip,
|
|
872
|
+
.kpi-grid:not(.pool-health-grid) > .kpi-card:nth-child(5n) .kpi-tip {
|
|
873
|
+
left: auto;
|
|
874
|
+
right: 12px;
|
|
875
|
+
}
|
|
866
876
|
.kpi-help:hover + .kpi-tip,
|
|
867
877
|
.kpi-help:focus-visible + .kpi-tip {
|
|
868
878
|
opacity: 1;
|
|
@@ -2015,7 +2025,7 @@ body.is-resizing-dag-graph {
|
|
|
2015
2025
|
.spec-evidence-back { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border: 1px solid var(--hairline); border-radius: var(--radius-pill); background: var(--surface); color: var(--ink); font-size: 12px; font-weight: 650; cursor: pointer; }
|
|
2016
2026
|
.spec-evidence-back:hover, .spec-evidence-back:focus-visible { background: var(--orange-soft); border-color: var(--orange); color: var(--orange-active); outline: none; }
|
|
2017
2027
|
.spec-evidence-detail-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 12px; color: var(--body); }
|
|
2018
|
-
.spec-evidence-detail-content { min-height: 0; max-width: 100%; margin: 0; padding: var(--space-3); border: 1px solid var(--hairline); border-radius: var(--radius-md); background: var(--canvas-soft);
|
|
2028
|
+
.spec-evidence-detail-content { min-height: 0; max-width: 100%; margin: 0; padding: var(--space-3); border: 1px solid var(--hairline); border-radius: var(--radius-md); background: var(--canvas-soft); overflow-wrap: anywhere; font-size: 12px; line-height: 1.6; }
|
|
2019
2029
|
.spec-evidence-detail-error { margin: 0; color: var(--red, #c00); font-size: 12px; }
|
|
2020
2030
|
.spec-evidence-detail-truncated { margin: 0; color: var(--muted); font-size: 11px; }
|
|
2021
2031
|
|
|
@@ -2289,6 +2299,16 @@ body.is-resizing-dag-graph {
|
|
|
2289
2299
|
.kpi-grid.pool-health-grid .kpi-card:first-child {
|
|
2290
2300
|
border-left: 0;
|
|
2291
2301
|
}
|
|
2302
|
+
/* Three-column: reset five-column tip edges, then rightmost column opens inward. */
|
|
2303
|
+
.kpi-grid:not(.pool-health-grid) > .kpi-card:nth-child(5n-1) .kpi-tip,
|
|
2304
|
+
.kpi-grid:not(.pool-health-grid) > .kpi-card:nth-child(5n) .kpi-tip {
|
|
2305
|
+
left: 12px;
|
|
2306
|
+
right: auto;
|
|
2307
|
+
}
|
|
2308
|
+
.kpi-grid:not(.pool-health-grid) > .kpi-card:nth-child(3n) .kpi-tip {
|
|
2309
|
+
left: auto;
|
|
2310
|
+
right: 12px;
|
|
2311
|
+
}
|
|
2292
2312
|
.panel-risk {
|
|
2293
2313
|
position: static;
|
|
2294
2314
|
}
|
|
@@ -2378,6 +2398,15 @@ body.is-resizing-dag-graph {
|
|
|
2378
2398
|
.kpi-grid.pool-health-grid .kpi-card:first-child {
|
|
2379
2399
|
border-left: 0;
|
|
2380
2400
|
}
|
|
2401
|
+
/* Two-column: reset three-column tip edges, then right column opens inward. */
|
|
2402
|
+
.kpi-grid:not(.pool-health-grid) > .kpi-card:nth-child(3n) .kpi-tip {
|
|
2403
|
+
left: 12px;
|
|
2404
|
+
right: auto;
|
|
2405
|
+
}
|
|
2406
|
+
.kpi-grid:not(.pool-health-grid) > .kpi-card:nth-child(2n) .kpi-tip {
|
|
2407
|
+
left: auto;
|
|
2408
|
+
right: 12px;
|
|
2409
|
+
}
|
|
2381
2410
|
.dag-inspector {
|
|
2382
2411
|
top: 12px;
|
|
2383
2412
|
right: 12px;
|
|
@@ -515,8 +515,8 @@ function renderSpecEvidenceDetail(content, dagRunId, nodeId) {
|
|
|
515
515
|
error.setAttribute("role", "alert");
|
|
516
516
|
wrapper.appendChild(error);
|
|
517
517
|
} else if (detail.data) {
|
|
518
|
-
const body = el("
|
|
519
|
-
body.
|
|
518
|
+
const body = el("div", "spec-evidence-detail-content");
|
|
519
|
+
body.appendChild(renderMarkdown(detail.data.content ?? ""));
|
|
520
520
|
wrapper.appendChild(body);
|
|
521
521
|
if (detail.data.truncated) {
|
|
522
522
|
const note = el(
|
|
@@ -10,7 +10,8 @@ import { getTaskPoolRoot } from "../pool/run-store.js";
|
|
|
10
10
|
import { checkRequiredOutputs } from "../outcomes/gate.js";
|
|
11
11
|
import { projectOutcome } from "../outcomes/projector.js";
|
|
12
12
|
import { writeOutcome } from "../outcomes/store.js";
|
|
13
|
-
|
|
13
|
+
/** No outer wall-clock by default; the DAG kernel owns liveness supervision. */
|
|
14
|
+
export const DEFAULT_RUN_DAG_TIMEOUT_MS = 0;
|
|
14
15
|
export const MAX_WORKER_TIMEOUT_MS = 7_200_000;
|
|
15
16
|
export async function runTaskSpec(options) {
|
|
16
17
|
let controllerIdentity = resolveControllerIdentity(options.client, options.controllerIdentity);
|
|
@@ -117,6 +118,13 @@ export async function runTaskSpec(options) {
|
|
|
117
118
|
options.repoRoot,
|
|
118
119
|
"--run-id",
|
|
119
120
|
workerRunId,
|
|
121
|
+
"--worker-association",
|
|
122
|
+
JSON.stringify({
|
|
123
|
+
schemaVersion: 1,
|
|
124
|
+
workerRunId,
|
|
125
|
+
featureId: options.taskSpec.feature_id,
|
|
126
|
+
taskId: options.taskSpec.id,
|
|
127
|
+
}),
|
|
120
128
|
"--events-jsonl",
|
|
121
129
|
dagEventsPath,
|
|
122
130
|
...maxConcurrentArgs(options.taskSpec),
|
|
@@ -339,6 +347,11 @@ async function runRequiredCommand(repoRoot, client, artifactName, args, expectJs
|
|
|
339
347
|
* not know the flag; their parser rejects before any DAG node can execute, so
|
|
340
348
|
* retrying the exact command without it is safe and preserves the same run id.
|
|
341
349
|
*/
|
|
350
|
+
function assertRunDagCommandOk(result, args) {
|
|
351
|
+
if (!result.ok) {
|
|
352
|
+
throw new Error(`loop-agent command failed: ${args.join(" ")}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
342
355
|
async function runDagWithEventsFallback(repoRoot, client, args, timeoutMs, eventCtx) {
|
|
343
356
|
const result = await client.run(args, {
|
|
344
357
|
cwd: repoRoot,
|
|
@@ -349,7 +362,7 @@ async function runDagWithEventsFallback(repoRoot, client, args, timeoutMs, event
|
|
|
349
362
|
if (result.ok)
|
|
350
363
|
return result;
|
|
351
364
|
if (!rejectsEventsJsonl(result) || !args.includes("--events-jsonl")) {
|
|
352
|
-
|
|
365
|
+
assertRunDagCommandOk(result, args);
|
|
353
366
|
}
|
|
354
367
|
const fallbackArgs = withoutFlagAndValue(args, "--events-jsonl");
|
|
355
368
|
eventCtx.progress.note("run-dag controller does not support --events-jsonl; retrying without DAG event stream");
|
|
@@ -359,9 +372,7 @@ async function runDagWithEventsFallback(repoRoot, client, args, timeoutMs, event
|
|
|
359
372
|
expectJson: true,
|
|
360
373
|
timeoutMs,
|
|
361
374
|
});
|
|
362
|
-
|
|
363
|
-
throw new Error(`loop-agent command failed: ${fallbackArgs.join(" ")}`);
|
|
364
|
-
}
|
|
375
|
+
assertRunDagCommandOk(fallback, fallbackArgs);
|
|
365
376
|
return fallback;
|
|
366
377
|
}
|
|
367
378
|
function rejectsEventsJsonl(result) {
|
|
@@ -417,8 +428,14 @@ async function runObservedStep(ctx, stepName, run, options) {
|
|
|
417
428
|
throw error;
|
|
418
429
|
}
|
|
419
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* Explicit worker.timeout_ms remains an operator hard limit. Omission disables
|
|
433
|
+
* the Worker wall clock so the DAG kernel can supervise active nodes.
|
|
434
|
+
*/
|
|
420
435
|
function resolveRunDagTimeoutMs(taskSpec) {
|
|
421
|
-
return
|
|
436
|
+
return taskSpec.worker.timeout_ms === undefined
|
|
437
|
+
? DEFAULT_RUN_DAG_TIMEOUT_MS
|
|
438
|
+
: Math.min(taskSpec.worker.timeout_ms, MAX_WORKER_TIMEOUT_MS);
|
|
422
439
|
}
|
|
423
440
|
async function collectFailureArtifacts(input) {
|
|
424
441
|
const doctor = await runObservedStep(input.eventCtx, "dag-doctor", () => input.client.run(["dag", "doctor", "--run-id", input.workerRunId, "--markdown"], {
|