@tea-agent/loop-agent 0.9.0 → 0.10.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 +42 -11
- package/README.md +20 -0
- package/dist/cli/command-definitions.js +7 -0
- package/dist/cli/program.js +6 -1
- package/dist/commands/dag-reconcile-run.js +118 -0
- package/dist/commands/init.js +12 -3
- package/dist/governance/manifest-types.js +4 -0
- package/dist/worker/cli.js +216 -0
- package/dist/worker/closeout/apply.js +73 -0
- package/dist/worker/closeout/preview.js +30 -0
- package/dist/worker/delivery/final-verification.js +158 -0
- package/dist/worker/delivery/git-transaction.js +354 -0
- package/dist/worker/delivery/package.js +449 -0
- package/dist/worker/feature/decision-loader.js +68 -0
- package/dist/worker/feature/discover.js +14 -0
- package/dist/worker/feature/next-action.js +74 -0
- package/dist/worker/feature/reducer.js +133 -0
- package/dist/worker/feature/review.js +502 -0
- package/dist/worker/feature/run.js +313 -0
- package/dist/worker/feature/types.js +1 -0
- package/dist/worker/follow-up/approve.js +270 -0
- package/dist/worker/follow-up/factory.js +234 -0
- package/dist/worker/follow-up/paths.js +25 -0
- package/dist/worker/follow-up/policy.js +26 -0
- package/dist/worker/follow-up/schema.js +93 -0
- package/dist/worker/follow-up/store.js +96 -0
- package/dist/worker/metrics/projector.js +139 -0
- package/dist/worker/observability/read-model.js +256 -15
- package/dist/worker/observe/paths.js +17 -5
- package/dist/worker/observe/static/app.js +443 -61
- package/dist/worker/observe/static/index.html +3 -1
- package/dist/worker/observe/static/styles.css +86 -19
- package/dist/worker/pool/run-store.js +14 -2
- package/dist/worker/pool/validation.js +59 -0
- package/dist/worker/report/morning-report.js +41 -6
- package/dist/worker/run-task/run-task.js +1 -1
- package/dist/worker/runner/run-ready.js +19 -5
- package/dist/workflows/dag/init-hybrid.js +3 -1
- package/dist/workflows/dag/lifecycle.js +146 -0
- package/dist/workflows/dag/node-execution.js +3 -0
- package/dist/workflows/dag/prompt.js +16 -0
- package/dist/workflows/dag/report.js +2 -0
- package/dist/workflows/dag/runner.js +133 -104
- package/dist/workflows/dag/types.js +3 -0
- package/docs/README.md +17 -0
- package/docs/agent-dag-recovery-playbook.md +1 -1
- package/docs/architecture/runtime-boundaries.md +3 -2
- package/docs/design/README.md +11 -5
- package/docs/exec-plans/active/README.md +1 -1
- package/docs/exec-plans/completed/README.md +8 -1
- package/docs/loop-agent-harness.md +45 -2
- package/docs/progress/README.md +2 -0
- package/docs/reports/README.md +10 -0
- package/docs/templates/agent-dag-report.schema.json +5 -3
- package/docs/templates/harness.schema.json +7 -2
- package/docs/templates/init-evolution-review.md +4 -2
- package/docs/verification-matrix.md +7 -0
- package/harness.json +4 -3
- package/package.json +4 -2
- package/scripts/check-product-line-docs.sh +7 -3
- package/skills/init-capability-evolution/SKILL.md +1 -0
- package/skills/loop-agent/references/command-reference.md +21 -0
- package/skills/loop-agent/references/hybrid-dag.md +4 -3
|
@@ -35,7 +35,9 @@
|
|
|
35
35
|
<h2>运行总览</h2>
|
|
36
36
|
<p>聚焦正在推进的 DAG、需要处理的风险与近期运行记录。</p>
|
|
37
37
|
</div>
|
|
38
|
-
<
|
|
38
|
+
<div id="dashboard-repo" class="repo-banner" aria-label="当前仓库"></div>
|
|
39
|
+
<section id="dashboard-features" class="panel panel-features" aria-label="Feature 决策"></section>
|
|
40
|
+
<section id="dashboard-kpi" class="panel panel-kpi" aria-label="运行摘要"></section>
|
|
39
41
|
<div class="dashboard-workspace">
|
|
40
42
|
<section id="dashboard-active-dags" class="panel panel-primary"></section>
|
|
41
43
|
<aside id="dashboard-risk" class="panel panel-risk" aria-label="运行风险"></aside>
|
|
@@ -238,12 +238,22 @@ td:first-child,
|
|
|
238
238
|
|
|
239
239
|
.view-dashboard { display: grid; gap: var(--space-4); }
|
|
240
240
|
|
|
241
|
+
.repo-banner { display: flex; align-items: center; gap: var(--space-3); padding: 10px 14px; border: 1px solid var(--hairline-strong); border-radius: var(--radius-lg); background: var(--surface); }
|
|
242
|
+
.repo-banner > i { color: var(--muted); font-size: 18px; }
|
|
243
|
+
.repo-banner-text { display: grid; gap: 2px; min-width: 0; }
|
|
244
|
+
.repo-banner-name { color: var(--ink); font-size: 14px; font-weight: 700; overflow-wrap: anywhere; }
|
|
245
|
+
.repo-banner-path { color: var(--muted); font-size: 11px; overflow-wrap: anywhere; }
|
|
246
|
+
|
|
241
247
|
.panel-kpi { padding: 0; background: transparent; border: 0; }
|
|
242
248
|
|
|
243
|
-
.kpi-grid
|
|
249
|
+
.kpi-group { margin: 0; padding: 0; border: 0; display: grid; gap: var(--space-3); }
|
|
250
|
+
.kpi-group + .kpi-group { margin-top: var(--space-3); }
|
|
251
|
+
.kpi-group-legend { padding: 0; color: var(--muted); font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
|
|
252
|
+
|
|
253
|
+
.kpi-grid {
|
|
244
254
|
display: grid;
|
|
245
255
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
246
|
-
overflow:
|
|
256
|
+
overflow: visible;
|
|
247
257
|
border: 1px solid var(--hairline-strong);
|
|
248
258
|
border-radius: var(--radius-lg);
|
|
249
259
|
background: var(--surface);
|
|
@@ -259,8 +269,15 @@ td:first-child,
|
|
|
259
269
|
|
|
260
270
|
.kpi-card:first-child { border-left: 0; }
|
|
261
271
|
.kpi-label { margin-bottom: 9px; color: var(--muted); font-size: 11px; font-weight: 650; }
|
|
272
|
+
.kpi-value-row { display: flex; align-items: baseline; gap: 6px; }
|
|
262
273
|
.kpi-value { color: var(--ink); font-size: 26px; font-weight: 500; line-height: 1; font-variant-numeric: tabular-nums; }
|
|
263
274
|
|
|
275
|
+
.kpi-help { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--muted-soft); cursor: help; font-size: 13px; line-height: 1; }
|
|
276
|
+
.kpi-help:hover, .kpi-help:focus-visible { color: var(--ink); background: var(--surface-muted); outline: none; }
|
|
277
|
+
.kpi-help:focus-visible { box-shadow: 0 0 0 2px var(--hairline-strong); }
|
|
278
|
+
.kpi-tip { position: absolute; left: 12px; right: 12px; top: calc(100% - 8px); z-index: 10; max-width: 280px; padding: 8px 10px; border: 1px solid var(--hairline-strong); border-radius: var(--radius-md); background: var(--surface-ink); color: var(--canvas); font-size: 11px; font-weight: 400; line-height: 1.5; box-shadow: var(--shadow-panel); opacity: 0; visibility: hidden; pointer-events: none; transition: opacity var(--transition-fast); }
|
|
279
|
+
.kpi-help:hover + .kpi-tip, .kpi-help:focus-visible + .kpi-tip { opacity: 1; visibility: visible; }
|
|
280
|
+
|
|
264
281
|
.dashboard-workspace,
|
|
265
282
|
.dashboard-secondary {
|
|
266
283
|
display: grid;
|
|
@@ -272,19 +289,29 @@ td:first-child,
|
|
|
272
289
|
.dashboard-secondary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
273
290
|
|
|
274
291
|
.panel-risk {
|
|
275
|
-
display:
|
|
276
|
-
grid-template-columns: minmax(130px, .28fr) minmax(0, 1fr) auto;
|
|
277
|
-
align-items: center;
|
|
278
|
-
gap: var(--space-4);
|
|
292
|
+
display: block;
|
|
279
293
|
background: var(--canvas-soft);
|
|
280
294
|
}
|
|
281
295
|
|
|
282
296
|
.panel-risk-attention { border-color: color-mix(in srgb, var(--red) 50%, var(--hairline)); background: #fff8f9; }
|
|
283
|
-
.panel-risk .section-heading { margin: 0; }
|
|
284
|
-
.risk-
|
|
297
|
+
.panel-risk .section-heading { margin: 0 0 var(--space-3); }
|
|
298
|
+
.risk-blocks { display: grid; gap: var(--space-4); }
|
|
299
|
+
.risk-block { display: grid; grid-template-columns: minmax(120px, .25fr) minmax(0, 1fr); align-items: start; gap: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--hairline); }
|
|
300
|
+
.risk-block:first-child { padding-top: 0; border-top: 0; }
|
|
301
|
+
.risk-block-label { color: var(--muted); font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
|
|
302
|
+
.risk-block-dag .risk-block-label { color: var(--ink); }
|
|
303
|
+
.risk-block-worker .risk-block-label { color: var(--ink); }
|
|
304
|
+
.risk-total { margin: 0; color: var(--amber); font-size: 32px; font-weight: 500; line-height: 1; font-variant-numeric: tabular-nums; }
|
|
285
305
|
.panel-risk-attention .risk-total { color: var(--red); }
|
|
286
306
|
.risk-summary { margin: 0; color: var(--body); font-size: 12px; line-height: 1.6; }
|
|
287
|
-
.panel-risk .risk-empty {
|
|
307
|
+
.panel-risk .risk-empty { margin: 0; }
|
|
308
|
+
|
|
309
|
+
.projection-fault { display: flex; align-items: flex-start; gap: var(--space-3); padding: 14px 16px; border: 1px solid color-mix(in srgb, var(--red) 50%, var(--hairline)); border-radius: var(--radius-lg); background: #fff8f9; }
|
|
310
|
+
.projection-fault > i { color: var(--red); font-size: 20px; line-height: 1.4; }
|
|
311
|
+
.projection-fault-body { display: grid; gap: 4px; min-width: 0; }
|
|
312
|
+
.projection-fault-title { margin: 0; color: var(--ink); font-size: 14px; font-weight: 700; }
|
|
313
|
+
.projection-fault-message { margin: 0; color: var(--body); font-size: 12px; overflow-wrap: anywhere; }
|
|
314
|
+
.projection-fault-at { margin: 0; color: var(--muted); font-size: 11px; }
|
|
288
315
|
|
|
289
316
|
.risk-link,
|
|
290
317
|
.active-dag-link,
|
|
@@ -383,7 +410,7 @@ tr.clickable:focus-within { outline: 2px solid var(--orange); outline-offset: -2
|
|
|
383
410
|
bottom: 24px;
|
|
384
411
|
display: grid;
|
|
385
412
|
grid-template-rows: auto minmax(0, 1fr);
|
|
386
|
-
width: min(440px, calc(100vw - 48px));
|
|
413
|
+
width: min(var(--dag-inspector-width, 440px), calc(100vw - 48px));
|
|
387
414
|
min-width: 0;
|
|
388
415
|
padding: 0;
|
|
389
416
|
overflow: hidden;
|
|
@@ -393,9 +420,37 @@ tr.clickable:focus-within { outline: 2px solid var(--orange); outline-offset: -2
|
|
|
393
420
|
pointer-events: none;
|
|
394
421
|
transform: translateX(calc(100% + 24px));
|
|
395
422
|
visibility: hidden;
|
|
396
|
-
transition: transform var(--transition-base), opacity var(--transition-base);
|
|
423
|
+
transition: transform var(--transition-base), opacity var(--transition-base), visibility 0s linear 240ms;
|
|
424
|
+
}
|
|
425
|
+
.dag-inspector.is-open { opacity: 1; pointer-events: auto; transform: translateX(0); visibility: visible; transition-delay: 0s, 0s, 0s; }
|
|
426
|
+
.dag-inspector-resize-handle {
|
|
427
|
+
position: absolute;
|
|
428
|
+
z-index: 1;
|
|
429
|
+
top: 0;
|
|
430
|
+
bottom: 0;
|
|
431
|
+
left: 0;
|
|
432
|
+
width: 18px;
|
|
433
|
+
cursor: col-resize;
|
|
434
|
+
touch-action: none;
|
|
435
|
+
outline: none;
|
|
436
|
+
}
|
|
437
|
+
.dag-inspector-resize-handle::before {
|
|
438
|
+
content: "";
|
|
439
|
+
position: absolute;
|
|
440
|
+
top: 50%;
|
|
441
|
+
left: 7px;
|
|
442
|
+
width: 3px;
|
|
443
|
+
height: 52px;
|
|
444
|
+
transform: translateY(-50%);
|
|
445
|
+
border-radius: 2px;
|
|
446
|
+
background: var(--hairline-strong);
|
|
447
|
+
opacity: 0;
|
|
448
|
+
transition: opacity var(--transition-base), background var(--transition-base);
|
|
397
449
|
}
|
|
398
|
-
.dag-inspector
|
|
450
|
+
.dag-inspector-resize-handle:hover::before,
|
|
451
|
+
.dag-inspector-resize-handle:focus-visible::before { background: var(--orange); opacity: 1; }
|
|
452
|
+
.dag-inspector-resize-handle:focus-visible { outline: 2px solid var(--orange); outline-offset: -2px; }
|
|
453
|
+
body.is-resizing-dag-inspector { cursor: col-resize; user-select: none; }
|
|
399
454
|
.dag-inspector-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-3); align-items: start; padding: var(--space-4) var(--space-4) var(--space-3); border-bottom: 1px solid var(--hairline); background: var(--canvas-soft); }
|
|
400
455
|
.dag-inspector-title { display: grid; gap: 6px; min-width: 0; }
|
|
401
456
|
.dag-inspector-title h3 { margin: 0; font-size: 14px; }
|
|
@@ -489,11 +544,23 @@ tr.clickable:focus-within { outline: 2px solid var(--orange); outline-offset: -2
|
|
|
489
544
|
.rank-node-id { color: var(--ink); font-size: 11px; }
|
|
490
545
|
.rank-node-dur { color: var(--muted); font-size: 11px; }
|
|
491
546
|
|
|
492
|
-
.process-timeline {
|
|
493
|
-
.process-timeline
|
|
494
|
-
.process-timeline-item
|
|
495
|
-
.process-timeline-time {
|
|
496
|
-
.process-timeline-
|
|
547
|
+
.process-timeline-note { margin: 0 0 var(--space-2); color: var(--muted); font-size: 11px; }
|
|
548
|
+
.process-timeline { display: grid; gap: 0; margin: 0; padding: var(--space-2) 0; list-style: none; }
|
|
549
|
+
.process-timeline-item { display: grid; grid-template-columns: 20px minmax(0, 1fr); align-items: stretch; min-height: 34px; }
|
|
550
|
+
.process-timeline-item.has-time { grid-template-columns: 20px 92px minmax(0, 1fr); }
|
|
551
|
+
.process-timeline-rail { position: relative; display: grid; place-items: center; min-height: 34px; }
|
|
552
|
+
.process-timeline-rail::before { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--hairline-strong); content: ""; }
|
|
553
|
+
.process-timeline-item.is-first .process-timeline-rail::before { top: 50%; }
|
|
554
|
+
.process-timeline-item.is-last .process-timeline-rail::before { bottom: 50%; }
|
|
555
|
+
.process-timeline-dot { position: relative; z-index: 1; width: 8px; height: 8px; border: 2px solid var(--surface); border-radius: 50%; background: var(--muted-soft); }
|
|
556
|
+
.process-timeline-lifecycle .process-timeline-dot { background: var(--lavender); }
|
|
557
|
+
.process-timeline-tool-start .process-timeline-dot { background: var(--orange); }
|
|
558
|
+
.process-timeline-tool-end .process-timeline-dot { background: var(--green); }
|
|
559
|
+
.process-timeline-assistant .process-timeline-dot { background: var(--blue); }
|
|
560
|
+
.process-timeline-protocol .process-timeline-dot { background: var(--muted-soft); }
|
|
561
|
+
.process-timeline-time { padding: 9px 0; color: var(--muted); font-size: 10px; font-variant-numeric: tabular-nums; }
|
|
562
|
+
.process-timeline-label { min-width: 0; padding: 8px 0 8px var(--space-2); color: var(--body); font-size: 12px; line-height: 1.5; overflow-wrap: anywhere; }
|
|
563
|
+
.process-timeline-protocol .process-timeline-label { color: var(--muted); font-size: 11px; }
|
|
497
564
|
|
|
498
565
|
.markdown-output { display: grid; gap: var(--space-3); color: var(--body); font-size: 13px; line-height: 1.65; }
|
|
499
566
|
.markdown-output > :first-child { margin-top: 0; }
|
|
@@ -579,9 +646,9 @@ tr.clickable:focus-within { outline: 2px solid var(--orange); outline-offset: -2
|
|
|
579
646
|
.kpi-card:nth-child(1), .kpi-card:nth-child(2) { border-top: 0; }
|
|
580
647
|
.kpi-card:nth-child(odd) { border-left: 0; }
|
|
581
648
|
.dag-inspector { top: 76px; right: 12px; bottom: 12px; width: calc(100vw - 24px); }
|
|
649
|
+
.dag-inspector-resize-handle { display: none; }
|
|
582
650
|
.dashboard-secondary { grid-template-columns: 1fr; }
|
|
583
|
-
.
|
|
584
|
-
.panel-risk .risk-empty { grid-column: auto; }
|
|
651
|
+
.risk-block { grid-template-columns: 1fr; gap: var(--space-2); }
|
|
585
652
|
.panel { padding: var(--space-4); }
|
|
586
653
|
.panel-table { overflow-x: auto; padding: 0; }
|
|
587
654
|
table { min-width: 680px; }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { appendFile, mkdir, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
export const TASK_POOL_RELATIVE_ROOT = ".harness/task-pool";
|
|
4
5
|
export function getTaskPoolRoot(repoRoot) {
|
|
@@ -19,6 +20,10 @@ export async function recordTaskPoolRun(input) {
|
|
|
19
20
|
await writeTaskPoolState(input.repoRoot, stateFromRun(input.run));
|
|
20
21
|
await appendJsonlFile(getEventsJsonlPath(input.repoRoot), eventFromRun(input.run));
|
|
21
22
|
}
|
|
23
|
+
export async function appendTaskPoolEvent(repoRoot, event) {
|
|
24
|
+
await ensurePoolDirs(repoRoot);
|
|
25
|
+
await appendJsonlFile(getEventsJsonlPath(repoRoot), event);
|
|
26
|
+
}
|
|
22
27
|
export async function findRunByWorkerRunId(repoRoot, workerRunId) {
|
|
23
28
|
const runs = await readJsonlFile(getRunsJsonlPath(repoRoot));
|
|
24
29
|
return runs.find((run) => run.workerRunId === workerRunId);
|
|
@@ -99,7 +104,14 @@ export async function readAllTaskPoolStates(repoRoot) {
|
|
|
99
104
|
export async function writeTaskPoolState(repoRoot, state) {
|
|
100
105
|
const statePath = getTaskStatePath(repoRoot, state.taskId);
|
|
101
106
|
await mkdir(path.dirname(statePath), { recursive: true });
|
|
102
|
-
|
|
107
|
+
const tempPath = path.join(path.dirname(statePath), `.${path.basename(statePath)}.${randomUUID()}.tmp`);
|
|
108
|
+
try {
|
|
109
|
+
await writeFile(tempPath, `${JSON.stringify({ schemaVersion: 1, ...state }, null, 2)}\n`, "utf-8");
|
|
110
|
+
await rename(tempPath, statePath);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
await unlink(tempPath).catch(() => { });
|
|
114
|
+
}
|
|
103
115
|
}
|
|
104
116
|
export async function readJsonlFile(filePath) {
|
|
105
117
|
try {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { getRunsJsonlPath, getTaskPoolRoot } from "./run-store.js";
|
|
5
|
+
export const taskPoolRunFactSchema = z.object({
|
|
6
|
+
schemaVersion: z.literal(1), batchRunId: z.string().min(1), workerRunId: z.string().min(1), taskId: z.string().min(1), featureId: z.string().min(1),
|
|
7
|
+
status: z.enum(["succeeded", "failed", "run-error"]), recordedAt: z.string().datetime(),
|
|
8
|
+
}).passthrough();
|
|
9
|
+
export const taskPoolStateFactSchema = z.object({
|
|
10
|
+
taskId: z.string().min(1), status: z.enum(["Draft", "Ready", "Queued", "Running", "AgentCompleted", "VerificationRunning", "HumanReview", "Done", "Failed", "Blocked", "Abandoned"]), updatedAt: z.string().datetime(),
|
|
11
|
+
}).passthrough();
|
|
12
|
+
export async function readValidTaskPoolRuns(repoRoot) {
|
|
13
|
+
const records = [];
|
|
14
|
+
const warnings = [];
|
|
15
|
+
try {
|
|
16
|
+
const raw = await readFile(getRunsJsonlPath(repoRoot), "utf-8");
|
|
17
|
+
for (const [index, line] of raw.split(/\r?\n/).filter(Boolean).entries())
|
|
18
|
+
try {
|
|
19
|
+
const parsed = taskPoolRunFactSchema.safeParse(JSON.parse(line));
|
|
20
|
+
if (parsed.success)
|
|
21
|
+
records.push(parsed.data);
|
|
22
|
+
else
|
|
23
|
+
warnings.push(`Task Pool runs.jsonl line ${index + 1} is semantically invalid`);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
warnings.push(`Task Pool runs.jsonl line ${index + 1} is corrupt`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (!isNotFound(error))
|
|
31
|
+
warnings.push("Task Pool runs.jsonl is unreadable");
|
|
32
|
+
}
|
|
33
|
+
return { records, warnings };
|
|
34
|
+
}
|
|
35
|
+
export async function readValidTaskPoolStates(repoRoot) {
|
|
36
|
+
const records = {};
|
|
37
|
+
const warnings = [];
|
|
38
|
+
const stateDir = path.join(getTaskPoolRoot(repoRoot), "states");
|
|
39
|
+
try {
|
|
40
|
+
for (const entry of await readdir(stateDir))
|
|
41
|
+
if (entry.endsWith(".json"))
|
|
42
|
+
try {
|
|
43
|
+
const parsed = taskPoolStateFactSchema.safeParse(JSON.parse(await readFile(path.join(stateDir, entry), "utf-8")));
|
|
44
|
+
if (parsed.success && parsed.data.taskId === entry.slice(0, -5))
|
|
45
|
+
records[parsed.data.taskId] = parsed.data;
|
|
46
|
+
else
|
|
47
|
+
warnings.push(`Task Pool state is semantically invalid: ${entry}`);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
warnings.push(`Task Pool state is corrupt: ${entry}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (!isNotFound(error))
|
|
55
|
+
warnings.push("Task Pool states are unreadable");
|
|
56
|
+
}
|
|
57
|
+
return { records, warnings };
|
|
58
|
+
}
|
|
59
|
+
function isNotFound(error) { return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT"); }
|
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
import { access, writeFile, mkdir } from "node:fs/promises";
|
|
1
|
+
import { access, writeFile, mkdir, readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import { followUpActionCardSchema } from "../follow-up/schema.js";
|
|
5
|
+
import { readFollowUpIndex, resolveRepoFile } from "../follow-up/store.js";
|
|
6
|
+
import { loadFeatureDecisionModels } from "../feature/decision-loader.js";
|
|
7
|
+
import { readValidTaskPoolRuns, readValidTaskPoolStates } from "../pool/validation.js";
|
|
4
8
|
export async function renderMorningReport(options) {
|
|
5
|
-
const
|
|
9
|
+
const { features } = await loadFeatureDecisionModels(options.repoRoot);
|
|
10
|
+
const allRuns = (await readValidTaskPoolRuns(options.repoRoot)).records;
|
|
11
|
+
const runs = allRuns.filter((run) => !options.batchRunId || run.batchRunId === options.batchRunId);
|
|
6
12
|
const total = runs.length;
|
|
7
13
|
const succeeded = runs.filter((run) => run.status === "succeeded").length;
|
|
8
14
|
const failed = runs.filter((run) => run.status === "failed").length;
|
|
9
15
|
const followUps = runs.filter((run) => run.failure).length;
|
|
10
|
-
const states = await
|
|
16
|
+
const states = (await readValidTaskPoolStates(options.repoRoot)).records;
|
|
11
17
|
const blocked = Object.values(states).filter((state) => state.status === "Blocked").length;
|
|
12
18
|
const lines = [
|
|
13
19
|
"# Nightly Worker Report",
|
|
14
20
|
"",
|
|
21
|
+
"## Feature Decision Summary",
|
|
22
|
+
"",
|
|
23
|
+
...(features.length === 0 ? ["- Status: no Feature Packet discovered", "- Next Action: add or locate a Feature Packet", "- Why: no shared Feature read model is available", "- Evidence: none"] : features.flatMap((feature) => {
|
|
24
|
+
const why = feature.blockingItems[0]?.message ?? `${feature.summary.tasksSucceeded}/${feature.summary.tasksTotal} tasks; ${feature.summary.requiredAcCovered}/${feature.summary.requiredAcTotal} required AC`;
|
|
25
|
+
const evidence = [feature.evidence.delivery, feature.evidence.closeout, feature.evidence.morningReport, feature.evidence.observeSnapshot, ...feature.blockingItems.flatMap((item) => item.evidence)].find(Boolean) ?? "none";
|
|
26
|
+
return [`### ${feature.featureId}`, "", `- Status: ${feature.status}`, `- Next Action: ${feature.nextAction?.command ?? feature.nextAction?.label ?? "none"}`, `- Why: ${why}`, `- Evidence: ${evidence}`, ""];
|
|
27
|
+
})),
|
|
15
28
|
"## Summary",
|
|
16
29
|
"",
|
|
17
30
|
`- Total: ${total}`,
|
|
@@ -26,16 +39,38 @@ export async function renderMorningReport(options) {
|
|
|
26
39
|
"|---|---|---|---|---|---|",
|
|
27
40
|
];
|
|
28
41
|
for (const run of runs) {
|
|
29
|
-
|
|
42
|
+
const followUp = await followUpSummary(run, options.repoRoot);
|
|
43
|
+
lines.push(`| ${run.taskId} | ${run.status} | ${run.workerRunId} | ${run.failure?.category ?? "-"} | ${followUp ?? run.failure?.derivedFollowUpTaskId ?? "-"} | ${await artifactSummary(run)} |`);
|
|
30
44
|
}
|
|
31
45
|
if (followUps > 0) {
|
|
32
46
|
lines.push("", "## Human Actions", "");
|
|
33
47
|
for (const run of runs.filter((candidate) => candidate.failure)) {
|
|
34
|
-
|
|
48
|
+
const followUp = await followUpSummary(run, options.repoRoot);
|
|
49
|
+
lines.push(`- ${followUp ?? `${run.failure?.derivedFollowUpTaskId}: ${run.failure?.recommendedFollowUpKind} for ${run.taskId}`}`);
|
|
35
50
|
}
|
|
36
51
|
}
|
|
37
52
|
return `${lines.join("\n")}\n`;
|
|
38
53
|
}
|
|
54
|
+
async function followUpSummary(run, repoRoot) {
|
|
55
|
+
if (!run.failure)
|
|
56
|
+
return undefined;
|
|
57
|
+
try {
|
|
58
|
+
const index = await readFollowUpIndex(repoRoot, run.featureId);
|
|
59
|
+
const entry = index.entries.find((candidate) => candidate.workerRunId === run.workerRunId && candidate.status !== "Superseded" && candidate.status !== "Rejected");
|
|
60
|
+
if (!entry)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (entry.kind === "TaskDraft") {
|
|
63
|
+
return entry.status === "Approved"
|
|
64
|
+
? `${entry.followUpId}: approved as ${entry.proposedTaskId}`
|
|
65
|
+
: `${entry.followUpId}: approve ${entry.proposedTaskId}`;
|
|
66
|
+
}
|
|
67
|
+
const card = followUpActionCardSchema.parse(YAML.parse(await readFile(await resolveRepoFile(repoRoot, entry.draftPath), "utf-8")));
|
|
68
|
+
return `${entry.followUpId}: ${card.command ?? card.label}`;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
39
74
|
export async function writeMorningReport(input) {
|
|
40
75
|
const markdown = await renderMorningReport(input);
|
|
41
76
|
await mkdir(path.dirname(input.outputPath), { recursive: true });
|
|
@@ -156,7 +156,7 @@ export async function runTaskSpec(options) {
|
|
|
156
156
|
taskArtifactsDir,
|
|
157
157
|
eventCtx,
|
|
158
158
|
});
|
|
159
|
-
if (reportDecision.succeeded) {
|
|
159
|
+
if (reportDecision.succeeded && !options.skipSuccessFinalization) {
|
|
160
160
|
await runObservedStep(eventCtx, "promote-run", async () => {
|
|
161
161
|
progress.step("promote run");
|
|
162
162
|
await runRequiredCommand(options.repoRoot, client, "promote-run", ["promote-run", materializeManifest.harnessTaskId, "--run-id", workerRunId], true, undefined, eventCtx);
|
|
@@ -66,6 +66,7 @@ export async function runReadyTasks(options) {
|
|
|
66
66
|
status: "reused",
|
|
67
67
|
runRecordPath: existing.runRecordPath,
|
|
68
68
|
});
|
|
69
|
+
await options.onTaskFinalized?.({ status: "reused", taskSpec, workerRunId, ...(existing.runRecordPath ? { runRecordPath: existing.runRecordPath } : {}) });
|
|
69
70
|
continue;
|
|
70
71
|
}
|
|
71
72
|
}
|
|
@@ -106,10 +107,12 @@ export async function runReadyTasks(options) {
|
|
|
106
107
|
client: options.client,
|
|
107
108
|
now,
|
|
108
109
|
workerRunId,
|
|
109
|
-
preflight:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
preflight: options.skipTaskPreflight
|
|
111
|
+
? false
|
|
112
|
+
: {
|
|
113
|
+
runCheckRepo: options.runCheckRepo,
|
|
114
|
+
...(options.checkRepoCommand ? { checkRepoCommand: options.checkRepoCommand } : {}),
|
|
115
|
+
},
|
|
113
116
|
progress,
|
|
114
117
|
...(options.piModel ? { piModel: options.piModel } : {}),
|
|
115
118
|
});
|
|
@@ -128,6 +131,7 @@ export async function runReadyTasks(options) {
|
|
|
128
131
|
status: "failed",
|
|
129
132
|
message,
|
|
130
133
|
});
|
|
134
|
+
let recorded = false;
|
|
131
135
|
try {
|
|
132
136
|
await recordTaskPoolRun({
|
|
133
137
|
repoRoot: options.repoRoot,
|
|
@@ -144,16 +148,20 @@ export async function runReadyTasks(options) {
|
|
|
144
148
|
...(retryOfWorkerRunId ? { retryOfWorkerRunId } : {}),
|
|
145
149
|
},
|
|
146
150
|
});
|
|
151
|
+
recorded = true;
|
|
147
152
|
}
|
|
148
153
|
catch (recordError) {
|
|
154
|
+
await options.onTaskFinalized?.({ status: "run-error", taskSpec, workerRunId });
|
|
149
155
|
tasks.push({
|
|
150
156
|
taskId,
|
|
151
157
|
workerRunId,
|
|
152
158
|
status: "record-error",
|
|
153
159
|
error: errorMessage(recordError),
|
|
154
160
|
});
|
|
155
|
-
|
|
161
|
+
break;
|
|
156
162
|
}
|
|
163
|
+
if (recorded)
|
|
164
|
+
await options.onTaskFinalized?.({ status: "run-error", taskSpec, workerRunId });
|
|
157
165
|
tasks.push({
|
|
158
166
|
taskId,
|
|
159
167
|
workerRunId,
|
|
@@ -207,8 +215,10 @@ export async function runReadyTasks(options) {
|
|
|
207
215
|
...(retryOfWorkerRunId ? { retryOfWorkerRunId } : {}),
|
|
208
216
|
...(result.failureArtifacts ? { failureArtifacts: result.failureArtifacts } : {}),
|
|
209
217
|
};
|
|
218
|
+
let recorded = false;
|
|
210
219
|
try {
|
|
211
220
|
await recordTaskPoolRun({ repoRoot: options.repoRoot, run });
|
|
221
|
+
recorded = true;
|
|
212
222
|
tasks.push({
|
|
213
223
|
taskId,
|
|
214
224
|
workerRunId: result.workerRunId,
|
|
@@ -217,6 +227,7 @@ export async function runReadyTasks(options) {
|
|
|
217
227
|
});
|
|
218
228
|
}
|
|
219
229
|
catch (error) {
|
|
230
|
+
await options.onTaskFinalized?.({ status: result.status, taskSpec, workerRunId: result.workerRunId, runRecordPath: result.runRecordPath });
|
|
220
231
|
tasks.push({
|
|
221
232
|
taskId,
|
|
222
233
|
workerRunId: result.workerRunId,
|
|
@@ -224,7 +235,10 @@ export async function runReadyTasks(options) {
|
|
|
224
235
|
error: errorMessage(error),
|
|
225
236
|
runRecordPath: result.runRecordPath,
|
|
226
237
|
});
|
|
238
|
+
break;
|
|
227
239
|
}
|
|
240
|
+
if (recorded)
|
|
241
|
+
await options.onTaskFinalized?.({ status: result.status, taskSpec, workerRunId: result.workerRunId, runRecordPath: result.runRecordPath });
|
|
228
242
|
}
|
|
229
243
|
const summary = summarize(tasks);
|
|
230
244
|
const batchRunPath = path.join(getTaskPoolRoot(options.repoRoot), "artifacts", batchRunId, "batch-run.json");
|
|
@@ -2,7 +2,7 @@ import { access, readdir, readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { assertValidDagSpec } from "./validate.js";
|
|
5
|
-
import { DEFAULT_DAG_EXECUTOR_MODELS, parseDagSpec, } from "./types.js";
|
|
5
|
+
import { DEFAULT_DAG_OUTPUT_LANGUAGE, DEFAULT_DAG_EXECUTOR_MODELS, parseDagSpec, } from "./types.js";
|
|
6
6
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
7
7
|
import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
|
|
8
8
|
import { resolveAdapter } from "../../adapters/index.js";
|
|
@@ -448,6 +448,7 @@ export async function loadTaskHybridSources(repoRoot, taskId) {
|
|
|
448
448
|
constraintPath,
|
|
449
449
|
requirementMarkdown,
|
|
450
450
|
constraintMarkdown,
|
|
451
|
+
outputLanguage: manifest.workflowPolicy.dag.outputLanguage,
|
|
451
452
|
referenceDocuments,
|
|
452
453
|
taskConfig,
|
|
453
454
|
enabledExecutors: resolveEnabledExecutors(manifest.executors),
|
|
@@ -524,6 +525,7 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
524
525
|
const spec = {
|
|
525
526
|
version: 2,
|
|
526
527
|
title: `Hybrid DAG: ${taskConfig.title}`,
|
|
528
|
+
outputLanguage: sources.outputLanguage ?? DEFAULT_DAG_OUTPUT_LANGUAGE,
|
|
527
529
|
objective: extractObjective(sources.requirementMarkdown, taskConfig.title),
|
|
528
530
|
successCriteria: extractSuccessCriteria(sources.requirementMarkdown, sources.taskId),
|
|
529
531
|
globalConstraints,
|