@tea-agent/loop-agent 0.35.0-beta.2 → 0.35.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +108 -108
- package/CHANGELOG.md +55 -4
- package/README.md +165 -165
- package/bin/agent-worker.js +0 -0
- package/bin/loop-agent.js +21 -21
- package/dist/application/task-lifecycle/advance.js +1 -0
- package/dist/commands/cursor-prompt.js +6 -6
- package/dist/commands/init-upgrade.js +351 -19
- package/dist/commands/init.js +14 -67
- package/dist/commands/loop-benchmark.js +11 -11
- package/dist/commands/pi-reuse-benchmark.js +16 -16
- package/dist/commands/run-dag-progress.js +14 -0
- package/dist/commands/task-advance.js +33 -3
- package/dist/shared/operator/capabilities.js +38 -1
- package/dist/sidecars/cursor-prompt/executor.js +1 -1
- package/dist/worker/console/chat/pi-runtime.js +41 -25
- package/dist/worker/console/chat/routes.js +27 -4
- package/dist/worker/console/operation-runner.js +24 -0
- package/dist/worker/console/operation-wait.js +241 -0
- package/dist/worker/console/operator-actions.js +58 -0
- package/dist/worker/console/static/assets/{index-hJqCPs_g.css → index-Dups4sSM.css} +1 -1
- package/dist/worker/console/static/assets/index-SjjjZnV3.js +56 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +141 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +13 -2
- package/dist/worker/console/static-src/operator-chat/useComposer.js +30 -7
- package/dist/worker/observe/static/copy.js +67 -67
- package/dist/worker/observe/static/dag-layout.d.ts +36 -36
- package/dist/worker/observe/static/dom.js +220 -220
- package/dist/worker/observe/static/relations.js +133 -133
- package/dist/worker/observe/static/run-processing.js +148 -148
- package/dist/worker/observe/static/views/batch.js +227 -227
- package/dist/worker/observe/static/views/failures.js +143 -143
- package/dist/worker/observe/static/views/feature.js +492 -492
- package/dist/worker/observe/static/views/run.js +453 -453
- package/dist/worker/observe/static/views/shell.js +7 -7
- package/dist/worker/observe/static/views/timeline.js +163 -163
- package/dist/workflows/dag/canvas-observer.js +275 -275
- package/dist/workflows/dag/frontend-prewrite-gate.js +9 -1
- package/dist/workflows/dag/init-hybrid.js +2 -0
- package/docs/architecture/evolution.md +73 -73
- package/docs/architecture/system-overview.md +100 -100
- package/docs/architecture/worker-and-feature.md +122 -122
- package/docs/skills/README.md +7 -7
- package/docs/templates/adr.md +60 -60
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
- package/docs/templates/agent-dag-report.schema.json +473 -473
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
- package/docs/templates/backend-test-result.schema.json +99 -99
- package/docs/templates/evaluation/agents-map-slim-v1.md +87 -87
- package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -153
- package/docs/templates/feature-spec.md +53 -53
- package/docs/templates/frontend-design-contract.md +42 -42
- package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -17
- package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -21
- package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -29
- package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -29
- package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -27
- package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -27
- package/docs/templates/frontend-eval/metrics.md +138 -138
- package/docs/templates/frontend-eval/smoke-targets.md +53 -53
- package/docs/templates/frontend-task-constraints.md +35 -35
- package/docs/templates/frontend-task-requirement.md +70 -70
- package/docs/templates/init-evolution-review.md +35 -35
- package/docs/templates/init-managed-agents.md +156 -154
- package/docs/templates/interactive-ui-round2-experiment.md +66 -66
- package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
- package/docs/templates/knowledge-sync-dag.json +178 -178
- package/docs/templates/knowledge-sync-draft.schema.json +71 -71
- package/docs/templates/product-line/closeout.yaml +9 -9
- package/docs/templates/product-line/design.md +13 -13
- package/docs/templates/product-line/links.md +10 -10
- package/docs/templates/product-line/requirement.md +17 -17
- package/docs/templates/product-line/test-plan.md +7 -7
- package/docs/templates/project-start-checklist.md +9 -9
- package/docs/templates/qa-report.md +48 -48
- package/docs/templates/sprint-contract.md +29 -29
- package/docs/templates/worker-dogfood-evidence.md +80 -80
- package/docs/templates/worker-dogfood-setup.md +68 -68
- package/harness.json +5 -2
- package/package.json +1 -1
- package/scripts/kb-bootstrap-init-skeleton.sh +0 -0
- package/scripts/kb-graph-incremental-prepare.mjs +0 -0
- package/scripts/kb-graph-materialize.mjs +105 -105
- package/scripts/kb-graph-promote.mjs +164 -164
- package/scripts/kb-query.mjs +554 -554
- package/skills/agent-worker/SKILL.md +48 -48
- package/skills/agent-worker/references/agent-worker-operator.md +159 -159
- package/skills/ai-engineering-context/SKILL.md +48 -48
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +0 -0
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +0 -0
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +0 -0
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +0 -0
- package/skills/analyze-product-requirements/scripts/compute-source-identity.mjs +0 -0
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +0 -0
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +0 -0
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +0 -0
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +0 -0
- package/skills/browser-tools/browser-content.js +103 -103
- package/skills/browser-tools/browser-cookies.js +35 -35
- package/skills/browser-tools/browser-eval.js +53 -53
- package/skills/browser-tools/browser-hn-scraper.js +108 -108
- package/skills/browser-tools/browser-nav.js +44 -44
- package/skills/browser-tools/browser-pick.js +162 -162
- package/skills/browser-tools/browser-screenshot.js +34 -34
- package/skills/browser-tools/browser-start.js +86 -86
- package/skills/browser-tools/package-lock.json +2556 -2556
- package/skills/browser-tools/package.json +19 -19
- package/skills/code-review-core/SKILL.md +20 -20
- package/skills/codebase-scout/SKILL.md +19 -19
- package/skills/grill-me/SKILL.md +10 -10
- package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +0 -0
- package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +0 -0
- package/skills/loop-agent/SKILL.md +1 -0
- package/skills/loop-agent/references/command-reference.md +641 -639
- package/skills/loop-agent/references/docs-converge.md +126 -126
- package/skills/loop-agent/references/learned/README.md +21 -21
- package/skills/loop-agent/references/pi-prompt.md +23 -23
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
- package/skills/playwright-cli/references/element-attributes.md +23 -23
- package/skills/playwright-cli/references/playwright-tests.md +39 -39
- package/skills/playwright-cli/references/request-mocking.md +87 -87
- package/skills/playwright-cli/references/running-code.md +241 -241
- package/skills/playwright-cli/references/session-management.md +225 -225
- package/skills/playwright-cli/references/storage-state.md +275 -275
- package/skills/playwright-cli/references/test-generation.md +433 -433
- package/skills/requesting-code-review/SKILL.md +101 -101
- package/skills/requesting-code-review/code-reviewer.md +168 -168
- package/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/skills/systematic-debugging/find-polluter.sh +63 -63
- package/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/skills/systematic-debugging/test-academic.md +14 -14
- package/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/skills/using-git-worktrees/SKILL.md +215 -215
- package/skills/verification-before-completion/SKILL.md +154 -154
- package/skills/webapp-testing/SKILL.md +19 -19
- package/dist/worker/console/static/assets/index-fsjzREob.js +0 -56
|
@@ -195,280 +195,280 @@ export function createDagCanvasObserver(opts) {
|
|
|
195
195
|
flush: () => writer.flush(),
|
|
196
196
|
};
|
|
197
197
|
}
|
|
198
|
-
const CANVAS_HEADER = `/* AUTO-GENERATED by . run-dag canvas observer. Derived view only — canonical state lives in .harness/dag-runs/. */
|
|
199
|
-
import {
|
|
200
|
-
Card,
|
|
201
|
-
CardBody,
|
|
202
|
-
CardHeader,
|
|
203
|
-
Divider,
|
|
204
|
-
Grid,
|
|
205
|
-
H1,
|
|
206
|
-
H2,
|
|
207
|
-
Pill,
|
|
208
|
-
Row,
|
|
209
|
-
Stack,
|
|
210
|
-
Stat,
|
|
211
|
-
Text,
|
|
212
|
-
computeDAGLayout,
|
|
213
|
-
useHostTheme,
|
|
214
|
-
} from 'cursor/canvas';
|
|
215
|
-
import { useMemo } from 'react';
|
|
216
|
-
|
|
217
|
-
type TaskStatus = 'PENDING' | 'RUNNING' | 'FINISHED' | 'ERROR' | 'SKIPPED';
|
|
218
|
-
type Complexity = 'HIGH' | 'MED' | 'LOW';
|
|
219
|
-
type Executor = 'cursor' | 'pi' | 'shell' | 'static';
|
|
220
|
-
|
|
221
|
-
interface TaskState {
|
|
222
|
-
id: string;
|
|
223
|
-
depends_on: string[];
|
|
224
|
-
complexity: Complexity;
|
|
225
|
-
executor: Executor;
|
|
226
|
-
subtask_prompt: string;
|
|
227
|
-
status: TaskStatus;
|
|
228
|
-
model: string;
|
|
229
|
-
startedAt?: number;
|
|
230
|
-
finishedAt?: number;
|
|
231
|
-
resultText?: string;
|
|
232
|
-
errorMessage?: string;
|
|
233
|
-
inputTokens?: number;
|
|
234
|
-
outputTokens?: number;
|
|
235
|
-
durationMs?: number;
|
|
236
|
-
skippedReason?: string;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
interface RunState {
|
|
240
|
-
title: string;
|
|
241
|
-
runId: string;
|
|
242
|
-
startedAt: number;
|
|
243
|
-
finishedAt?: number;
|
|
244
|
-
runOutcome?: 'SUCCESS' | 'FAILED' | 'INTERRUPTED' | 'PAUSED';
|
|
245
|
-
runMessage?: string;
|
|
246
|
-
tasks: TaskState[];
|
|
198
|
+
const CANVAS_HEADER = `/* AUTO-GENERATED by . run-dag canvas observer. Derived view only — canonical state lives in .harness/dag-runs/. */
|
|
199
|
+
import {
|
|
200
|
+
Card,
|
|
201
|
+
CardBody,
|
|
202
|
+
CardHeader,
|
|
203
|
+
Divider,
|
|
204
|
+
Grid,
|
|
205
|
+
H1,
|
|
206
|
+
H2,
|
|
207
|
+
Pill,
|
|
208
|
+
Row,
|
|
209
|
+
Stack,
|
|
210
|
+
Stat,
|
|
211
|
+
Text,
|
|
212
|
+
computeDAGLayout,
|
|
213
|
+
useHostTheme,
|
|
214
|
+
} from 'cursor/canvas';
|
|
215
|
+
import { useMemo } from 'react';
|
|
216
|
+
|
|
217
|
+
type TaskStatus = 'PENDING' | 'RUNNING' | 'FINISHED' | 'ERROR' | 'SKIPPED';
|
|
218
|
+
type Complexity = 'HIGH' | 'MED' | 'LOW';
|
|
219
|
+
type Executor = 'cursor' | 'pi' | 'shell' | 'static';
|
|
220
|
+
|
|
221
|
+
interface TaskState {
|
|
222
|
+
id: string;
|
|
223
|
+
depends_on: string[];
|
|
224
|
+
complexity: Complexity;
|
|
225
|
+
executor: Executor;
|
|
226
|
+
subtask_prompt: string;
|
|
227
|
+
status: TaskStatus;
|
|
228
|
+
model: string;
|
|
229
|
+
startedAt?: number;
|
|
230
|
+
finishedAt?: number;
|
|
231
|
+
resultText?: string;
|
|
232
|
+
errorMessage?: string;
|
|
233
|
+
inputTokens?: number;
|
|
234
|
+
outputTokens?: number;
|
|
235
|
+
durationMs?: number;
|
|
236
|
+
skippedReason?: string;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
interface RunState {
|
|
240
|
+
title: string;
|
|
241
|
+
runId: string;
|
|
242
|
+
startedAt: number;
|
|
243
|
+
finishedAt?: number;
|
|
244
|
+
runOutcome?: 'SUCCESS' | 'FAILED' | 'INTERRUPTED' | 'PAUSED';
|
|
245
|
+
runMessage?: string;
|
|
246
|
+
tasks: TaskState[];
|
|
247
247
|
}`;
|
|
248
|
-
const CANVAS_BODY = String.raw `const NODE_W = 200;
|
|
249
|
-
const NODE_H = 72;
|
|
250
|
-
|
|
251
|
-
function pillToneFor(status: TaskStatus): 'neutral' | 'info' | 'success' | 'warning' {
|
|
252
|
-
switch (status) {
|
|
253
|
-
case 'PENDING':
|
|
254
|
-
return 'neutral';
|
|
255
|
-
case 'RUNNING':
|
|
256
|
-
return 'info';
|
|
257
|
-
case 'FINISHED':
|
|
258
|
-
return 'success';
|
|
259
|
-
case 'ERROR':
|
|
260
|
-
return 'warning';
|
|
261
|
-
case 'SKIPPED':
|
|
262
|
-
return 'neutral';
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function formatDuration(ms?: number): string {
|
|
267
|
-
if (ms === undefined) return '—';
|
|
268
|
-
if (ms < 1000) return ms + 'ms';
|
|
269
|
-
const s = ms / 1000;
|
|
270
|
-
if (s < 60) return s.toFixed(1) + 's';
|
|
271
|
-
const m = Math.floor(s / 60);
|
|
272
|
-
const rem = Math.round(s - m * 60);
|
|
273
|
-
return m + 'm ' + rem + 's';
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function elapsed(state: RunState): number {
|
|
277
|
-
const end = state.finishedAt ?? Date.now();
|
|
278
|
-
return end - state.startedAt;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function DAGGraph({ state }: { state: RunState }): JSX.Element {
|
|
282
|
-
const theme = useHostTheme();
|
|
283
|
-
const layout = computeDAGLayout({
|
|
284
|
-
nodes: state.tasks.map((t) => ({ id: t.id })),
|
|
285
|
-
edges: state.tasks.flatMap((t) =>
|
|
286
|
-
t.depends_on.map((d) => ({ from: d, to: t.id })),
|
|
287
|
-
),
|
|
288
|
-
direction: 'vertical',
|
|
289
|
-
nodeWidth: NODE_W,
|
|
290
|
-
nodeHeight: NODE_H,
|
|
291
|
-
rankGap: 72,
|
|
292
|
-
nodeGap: 40,
|
|
293
|
-
padding: 24,
|
|
294
|
-
});
|
|
295
|
-
const byId = new Map(state.tasks.map((t) => [t.id, t]));
|
|
296
|
-
|
|
297
|
-
return (
|
|
298
|
-
<svg width={layout.width} height={layout.height} style={{ display: 'block', maxWidth: '100%' }}>
|
|
299
|
-
{layout.edges.map((e, i) => (
|
|
300
|
-
<line
|
|
301
|
-
key={'e' + i}
|
|
302
|
-
x1={e.sourceX}
|
|
303
|
-
y1={e.sourceY}
|
|
304
|
-
x2={e.targetX}
|
|
305
|
-
y2={e.targetY}
|
|
306
|
-
stroke={theme.stroke.secondary}
|
|
307
|
-
strokeWidth={1.25}
|
|
308
|
-
markerEnd="url(#dag-arrow)"
|
|
309
|
-
/>
|
|
310
|
-
))}
|
|
311
|
-
<defs>
|
|
312
|
-
<marker id="dag-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
|
313
|
-
<path d="M 0 0 L 10 5 L 0 10 z" fill={theme.stroke.secondary} />
|
|
314
|
-
</marker>
|
|
315
|
-
</defs>
|
|
316
|
-
{layout.nodes.map((n) => {
|
|
317
|
-
const t = byId.get(n.id);
|
|
318
|
-
if (!t) return null;
|
|
319
|
-
return (
|
|
320
|
-
<g key={n.id} transform={'translate(' + n.x + ',' + n.y + ')'}>
|
|
321
|
-
<rect
|
|
322
|
-
width={NODE_W}
|
|
323
|
-
height={NODE_H}
|
|
324
|
-
rx={8}
|
|
325
|
-
ry={8}
|
|
326
|
-
fill={theme.fill.secondary}
|
|
327
|
-
stroke={theme.stroke.secondary}
|
|
328
|
-
strokeWidth={t.status === 'RUNNING' ? 2 : 1}
|
|
329
|
-
/>
|
|
330
|
-
<text x={12} y={22} fontSize={13} fontWeight={600} fill={theme.text.primary}>
|
|
331
|
-
{t.id.length > 22 ? t.id.slice(0, 21) + '…' : t.id}
|
|
332
|
-
</text>
|
|
333
|
-
<text x={12} y={42} fontSize={10.5} fill={theme.text.secondary}>
|
|
334
|
-
{t.executor} · {t.complexity} · {t.status}
|
|
335
|
-
</text>
|
|
336
|
-
<text x={12} y={58} fontSize={10.5} fill={theme.text.tertiary}>
|
|
337
|
-
{formatDuration(t.durationMs)}
|
|
338
|
-
</text>
|
|
339
|
-
</g>
|
|
340
|
-
);
|
|
341
|
-
})}
|
|
342
|
-
</svg>
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
export default function DagRun(): JSX.Element {
|
|
347
|
-
const counts = useMemo(
|
|
348
|
-
() =>
|
|
349
|
-
STATE.tasks.reduce(
|
|
350
|
-
(acc, t) => {
|
|
351
|
-
acc.total += 1;
|
|
352
|
-
const key = t.status.toLowerCase() as 'pending' | 'running' | 'finished' | 'error' | 'skipped';
|
|
353
|
-
if (key in acc) acc[key] += 1;
|
|
354
|
-
return acc;
|
|
355
|
-
},
|
|
356
|
-
{ total: 0, pending: 0, running: 0, finished: 0, error: 0, skipped: 0 },
|
|
357
|
-
),
|
|
358
|
-
[],
|
|
359
|
-
);
|
|
360
|
-
const isFinal = STATE.finishedAt !== undefined;
|
|
361
|
-
const statusLabel =
|
|
362
|
-
STATE.runOutcome === 'PAUSED'
|
|
363
|
-
? 'PAUSED'
|
|
364
|
-
: STATE.runOutcome === 'FAILED'
|
|
365
|
-
? 'FAILED'
|
|
366
|
-
: isFinal
|
|
367
|
-
? 'COMPLETE'
|
|
368
|
-
: 'RUNNING';
|
|
369
|
-
const statusTone =
|
|
370
|
-
STATE.runOutcome === 'FAILED'
|
|
371
|
-
? 'danger'
|
|
372
|
-
: STATE.runOutcome === 'PAUSED'
|
|
373
|
-
? 'warning'
|
|
374
|
-
: isFinal
|
|
375
|
-
? 'success'
|
|
376
|
-
: 'info';
|
|
377
|
-
|
|
378
|
-
return (
|
|
379
|
-
<Stack gap={20}>
|
|
380
|
-
<Stack gap={6}>
|
|
381
|
-
<H1>{STATE.title}</H1>
|
|
382
|
-
<Text tone="secondary" size="small">
|
|
383
|
-
run {STATE.runId}
|
|
384
|
-
</Text>
|
|
385
|
-
<Row gap={10} align="center">
|
|
386
|
-
<Pill tone={statusTone} active size="sm">
|
|
387
|
-
{statusLabel}
|
|
388
|
-
</Pill>
|
|
389
|
-
<Text tone="secondary" size="small">
|
|
390
|
-
{counts.total} tasks · elapsed {formatDuration(elapsed(STATE))}
|
|
391
|
-
</Text>
|
|
392
|
-
</Row>
|
|
393
|
-
{STATE.runMessage ? (
|
|
394
|
-
<Text tone="secondary" size="small">
|
|
395
|
-
{STATE.runMessage}
|
|
396
|
-
</Text>
|
|
397
|
-
) : null}
|
|
398
|
-
</Stack>
|
|
399
|
-
|
|
400
|
-
<Grid columns={6} gap={12}>
|
|
401
|
-
<Stat value={String(counts.total)} label="Total" />
|
|
402
|
-
<Stat value={String(counts.pending)} label="Pending" />
|
|
403
|
-
<Stat value={String(counts.running)} label="Running" tone={counts.running > 0 ? 'info' : undefined} />
|
|
404
|
-
<Stat value={String(counts.finished)} label="Finished" tone={counts.finished > 0 ? 'success' : undefined} />
|
|
405
|
-
<Stat value={String(counts.error)} label="Errored" tone={counts.error > 0 ? 'danger' : undefined} />
|
|
406
|
-
<Stat value={String(counts.skipped)} label="Skipped" />
|
|
407
|
-
</Grid>
|
|
408
|
-
|
|
409
|
-
<Divider />
|
|
410
|
-
|
|
411
|
-
<Stack gap={12}>
|
|
412
|
-
<H2>Graph</H2>
|
|
413
|
-
<DAGGraph state={STATE} />
|
|
414
|
-
</Stack>
|
|
415
|
-
|
|
416
|
-
<Divider />
|
|
417
|
-
|
|
418
|
-
<Stack gap={12}>
|
|
419
|
-
<H2>Tasks</H2>
|
|
420
|
-
<Stack gap={10}>
|
|
421
|
-
{STATE.tasks.map((t) => (
|
|
422
|
-
<Card key={t.id} collapsible defaultOpen={t.status !== 'PENDING'}>
|
|
423
|
-
<CardHeader
|
|
424
|
-
trailing={
|
|
425
|
-
<Row gap={6} align="center">
|
|
426
|
-
<Pill tone="neutral" size="sm">
|
|
427
|
-
{t.executor}
|
|
428
|
-
</Pill>
|
|
429
|
-
<Pill tone={pillToneFor(t.status)} active={t.status !== 'PENDING'} size="sm">
|
|
430
|
-
{t.status}
|
|
431
|
-
</Pill>
|
|
432
|
-
</Row>
|
|
433
|
-
}
|
|
434
|
-
>
|
|
435
|
-
{t.id}
|
|
436
|
-
</CardHeader>
|
|
437
|
-
<CardBody>
|
|
438
|
-
<Stack gap={8}>
|
|
439
|
-
<Text tone="secondary" size="small">
|
|
440
|
-
Model {t.model}
|
|
441
|
-
{t.depends_on.length > 0 ? ' · depends on ' + t.depends_on.join(', ') : ''}
|
|
442
|
-
{t.durationMs !== undefined ? ' · ' + formatDuration(t.durationMs) : ''}
|
|
443
|
-
</Text>
|
|
444
|
-
<Text size="small">
|
|
445
|
-
<Text as="span" weight="semibold">
|
|
446
|
-
Prompt:{' '}
|
|
447
|
-
</Text>
|
|
448
|
-
{t.subtask_prompt}
|
|
449
|
-
</Text>
|
|
450
|
-
{t.resultText ? (
|
|
451
|
-
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
|
452
|
-
{t.resultText}
|
|
453
|
-
{t.status === 'RUNNING' ? '\u2588' : ''}
|
|
454
|
-
</pre>
|
|
455
|
-
) : null}
|
|
456
|
-
{t.skippedReason ? (
|
|
457
|
-
<Text size="small" tone="secondary">
|
|
458
|
-
Skipped: {t.skippedReason}
|
|
459
|
-
</Text>
|
|
460
|
-
) : null}
|
|
461
|
-
{t.errorMessage ? (
|
|
462
|
-
<Text size="small" tone="secondary">
|
|
463
|
-
{t.errorMessage}
|
|
464
|
-
</Text>
|
|
465
|
-
) : null}
|
|
466
|
-
</Stack>
|
|
467
|
-
</CardBody>
|
|
468
|
-
</Card>
|
|
469
|
-
))}
|
|
470
|
-
</Stack>
|
|
471
|
-
</Stack>
|
|
472
|
-
</Stack>
|
|
473
|
-
);
|
|
248
|
+
const CANVAS_BODY = String.raw `const NODE_W = 200;
|
|
249
|
+
const NODE_H = 72;
|
|
250
|
+
|
|
251
|
+
function pillToneFor(status: TaskStatus): 'neutral' | 'info' | 'success' | 'warning' {
|
|
252
|
+
switch (status) {
|
|
253
|
+
case 'PENDING':
|
|
254
|
+
return 'neutral';
|
|
255
|
+
case 'RUNNING':
|
|
256
|
+
return 'info';
|
|
257
|
+
case 'FINISHED':
|
|
258
|
+
return 'success';
|
|
259
|
+
case 'ERROR':
|
|
260
|
+
return 'warning';
|
|
261
|
+
case 'SKIPPED':
|
|
262
|
+
return 'neutral';
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function formatDuration(ms?: number): string {
|
|
267
|
+
if (ms === undefined) return '—';
|
|
268
|
+
if (ms < 1000) return ms + 'ms';
|
|
269
|
+
const s = ms / 1000;
|
|
270
|
+
if (s < 60) return s.toFixed(1) + 's';
|
|
271
|
+
const m = Math.floor(s / 60);
|
|
272
|
+
const rem = Math.round(s - m * 60);
|
|
273
|
+
return m + 'm ' + rem + 's';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function elapsed(state: RunState): number {
|
|
277
|
+
const end = state.finishedAt ?? Date.now();
|
|
278
|
+
return end - state.startedAt;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function DAGGraph({ state }: { state: RunState }): JSX.Element {
|
|
282
|
+
const theme = useHostTheme();
|
|
283
|
+
const layout = computeDAGLayout({
|
|
284
|
+
nodes: state.tasks.map((t) => ({ id: t.id })),
|
|
285
|
+
edges: state.tasks.flatMap((t) =>
|
|
286
|
+
t.depends_on.map((d) => ({ from: d, to: t.id })),
|
|
287
|
+
),
|
|
288
|
+
direction: 'vertical',
|
|
289
|
+
nodeWidth: NODE_W,
|
|
290
|
+
nodeHeight: NODE_H,
|
|
291
|
+
rankGap: 72,
|
|
292
|
+
nodeGap: 40,
|
|
293
|
+
padding: 24,
|
|
294
|
+
});
|
|
295
|
+
const byId = new Map(state.tasks.map((t) => [t.id, t]));
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<svg width={layout.width} height={layout.height} style={{ display: 'block', maxWidth: '100%' }}>
|
|
299
|
+
{layout.edges.map((e, i) => (
|
|
300
|
+
<line
|
|
301
|
+
key={'e' + i}
|
|
302
|
+
x1={e.sourceX}
|
|
303
|
+
y1={e.sourceY}
|
|
304
|
+
x2={e.targetX}
|
|
305
|
+
y2={e.targetY}
|
|
306
|
+
stroke={theme.stroke.secondary}
|
|
307
|
+
strokeWidth={1.25}
|
|
308
|
+
markerEnd="url(#dag-arrow)"
|
|
309
|
+
/>
|
|
310
|
+
))}
|
|
311
|
+
<defs>
|
|
312
|
+
<marker id="dag-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
|
313
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill={theme.stroke.secondary} />
|
|
314
|
+
</marker>
|
|
315
|
+
</defs>
|
|
316
|
+
{layout.nodes.map((n) => {
|
|
317
|
+
const t = byId.get(n.id);
|
|
318
|
+
if (!t) return null;
|
|
319
|
+
return (
|
|
320
|
+
<g key={n.id} transform={'translate(' + n.x + ',' + n.y + ')'}>
|
|
321
|
+
<rect
|
|
322
|
+
width={NODE_W}
|
|
323
|
+
height={NODE_H}
|
|
324
|
+
rx={8}
|
|
325
|
+
ry={8}
|
|
326
|
+
fill={theme.fill.secondary}
|
|
327
|
+
stroke={theme.stroke.secondary}
|
|
328
|
+
strokeWidth={t.status === 'RUNNING' ? 2 : 1}
|
|
329
|
+
/>
|
|
330
|
+
<text x={12} y={22} fontSize={13} fontWeight={600} fill={theme.text.primary}>
|
|
331
|
+
{t.id.length > 22 ? t.id.slice(0, 21) + '…' : t.id}
|
|
332
|
+
</text>
|
|
333
|
+
<text x={12} y={42} fontSize={10.5} fill={theme.text.secondary}>
|
|
334
|
+
{t.executor} · {t.complexity} · {t.status}
|
|
335
|
+
</text>
|
|
336
|
+
<text x={12} y={58} fontSize={10.5} fill={theme.text.tertiary}>
|
|
337
|
+
{formatDuration(t.durationMs)}
|
|
338
|
+
</text>
|
|
339
|
+
</g>
|
|
340
|
+
);
|
|
341
|
+
})}
|
|
342
|
+
</svg>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export default function DagRun(): JSX.Element {
|
|
347
|
+
const counts = useMemo(
|
|
348
|
+
() =>
|
|
349
|
+
STATE.tasks.reduce(
|
|
350
|
+
(acc, t) => {
|
|
351
|
+
acc.total += 1;
|
|
352
|
+
const key = t.status.toLowerCase() as 'pending' | 'running' | 'finished' | 'error' | 'skipped';
|
|
353
|
+
if (key in acc) acc[key] += 1;
|
|
354
|
+
return acc;
|
|
355
|
+
},
|
|
356
|
+
{ total: 0, pending: 0, running: 0, finished: 0, error: 0, skipped: 0 },
|
|
357
|
+
),
|
|
358
|
+
[],
|
|
359
|
+
);
|
|
360
|
+
const isFinal = STATE.finishedAt !== undefined;
|
|
361
|
+
const statusLabel =
|
|
362
|
+
STATE.runOutcome === 'PAUSED'
|
|
363
|
+
? 'PAUSED'
|
|
364
|
+
: STATE.runOutcome === 'FAILED'
|
|
365
|
+
? 'FAILED'
|
|
366
|
+
: isFinal
|
|
367
|
+
? 'COMPLETE'
|
|
368
|
+
: 'RUNNING';
|
|
369
|
+
const statusTone =
|
|
370
|
+
STATE.runOutcome === 'FAILED'
|
|
371
|
+
? 'danger'
|
|
372
|
+
: STATE.runOutcome === 'PAUSED'
|
|
373
|
+
? 'warning'
|
|
374
|
+
: isFinal
|
|
375
|
+
? 'success'
|
|
376
|
+
: 'info';
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<Stack gap={20}>
|
|
380
|
+
<Stack gap={6}>
|
|
381
|
+
<H1>{STATE.title}</H1>
|
|
382
|
+
<Text tone="secondary" size="small">
|
|
383
|
+
run {STATE.runId}
|
|
384
|
+
</Text>
|
|
385
|
+
<Row gap={10} align="center">
|
|
386
|
+
<Pill tone={statusTone} active size="sm">
|
|
387
|
+
{statusLabel}
|
|
388
|
+
</Pill>
|
|
389
|
+
<Text tone="secondary" size="small">
|
|
390
|
+
{counts.total} tasks · elapsed {formatDuration(elapsed(STATE))}
|
|
391
|
+
</Text>
|
|
392
|
+
</Row>
|
|
393
|
+
{STATE.runMessage ? (
|
|
394
|
+
<Text tone="secondary" size="small">
|
|
395
|
+
{STATE.runMessage}
|
|
396
|
+
</Text>
|
|
397
|
+
) : null}
|
|
398
|
+
</Stack>
|
|
399
|
+
|
|
400
|
+
<Grid columns={6} gap={12}>
|
|
401
|
+
<Stat value={String(counts.total)} label="Total" />
|
|
402
|
+
<Stat value={String(counts.pending)} label="Pending" />
|
|
403
|
+
<Stat value={String(counts.running)} label="Running" tone={counts.running > 0 ? 'info' : undefined} />
|
|
404
|
+
<Stat value={String(counts.finished)} label="Finished" tone={counts.finished > 0 ? 'success' : undefined} />
|
|
405
|
+
<Stat value={String(counts.error)} label="Errored" tone={counts.error > 0 ? 'danger' : undefined} />
|
|
406
|
+
<Stat value={String(counts.skipped)} label="Skipped" />
|
|
407
|
+
</Grid>
|
|
408
|
+
|
|
409
|
+
<Divider />
|
|
410
|
+
|
|
411
|
+
<Stack gap={12}>
|
|
412
|
+
<H2>Graph</H2>
|
|
413
|
+
<DAGGraph state={STATE} />
|
|
414
|
+
</Stack>
|
|
415
|
+
|
|
416
|
+
<Divider />
|
|
417
|
+
|
|
418
|
+
<Stack gap={12}>
|
|
419
|
+
<H2>Tasks</H2>
|
|
420
|
+
<Stack gap={10}>
|
|
421
|
+
{STATE.tasks.map((t) => (
|
|
422
|
+
<Card key={t.id} collapsible defaultOpen={t.status !== 'PENDING'}>
|
|
423
|
+
<CardHeader
|
|
424
|
+
trailing={
|
|
425
|
+
<Row gap={6} align="center">
|
|
426
|
+
<Pill tone="neutral" size="sm">
|
|
427
|
+
{t.executor}
|
|
428
|
+
</Pill>
|
|
429
|
+
<Pill tone={pillToneFor(t.status)} active={t.status !== 'PENDING'} size="sm">
|
|
430
|
+
{t.status}
|
|
431
|
+
</Pill>
|
|
432
|
+
</Row>
|
|
433
|
+
}
|
|
434
|
+
>
|
|
435
|
+
{t.id}
|
|
436
|
+
</CardHeader>
|
|
437
|
+
<CardBody>
|
|
438
|
+
<Stack gap={8}>
|
|
439
|
+
<Text tone="secondary" size="small">
|
|
440
|
+
Model {t.model}
|
|
441
|
+
{t.depends_on.length > 0 ? ' · depends on ' + t.depends_on.join(', ') : ''}
|
|
442
|
+
{t.durationMs !== undefined ? ' · ' + formatDuration(t.durationMs) : ''}
|
|
443
|
+
</Text>
|
|
444
|
+
<Text size="small">
|
|
445
|
+
<Text as="span" weight="semibold">
|
|
446
|
+
Prompt:{' '}
|
|
447
|
+
</Text>
|
|
448
|
+
{t.subtask_prompt}
|
|
449
|
+
</Text>
|
|
450
|
+
{t.resultText ? (
|
|
451
|
+
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
|
452
|
+
{t.resultText}
|
|
453
|
+
{t.status === 'RUNNING' ? '\u2588' : ''}
|
|
454
|
+
</pre>
|
|
455
|
+
) : null}
|
|
456
|
+
{t.skippedReason ? (
|
|
457
|
+
<Text size="small" tone="secondary">
|
|
458
|
+
Skipped: {t.skippedReason}
|
|
459
|
+
</Text>
|
|
460
|
+
) : null}
|
|
461
|
+
{t.errorMessage ? (
|
|
462
|
+
<Text size="small" tone="secondary">
|
|
463
|
+
{t.errorMessage}
|
|
464
|
+
</Text>
|
|
465
|
+
) : null}
|
|
466
|
+
</Stack>
|
|
467
|
+
</CardBody>
|
|
468
|
+
</Card>
|
|
469
|
+
))}
|
|
470
|
+
</Stack>
|
|
471
|
+
</Stack>
|
|
472
|
+
</Stack>
|
|
473
|
+
);
|
|
474
474
|
}`;
|
|
@@ -184,7 +184,15 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
184
184
|
const raw = JSON.parse(await readFile(artifact.path, "utf8"));
|
|
185
185
|
const contract = frontendImplementationContractSchema.parse(raw);
|
|
186
186
|
if (!input.config.allowedMockStrategies.includes(contract.mockApi.strategy)) {
|
|
187
|
-
|
|
187
|
+
const quotedAllowed = input.config.allowedMockStrategies
|
|
188
|
+
.map((strategy) => JSON.stringify(strategy))
|
|
189
|
+
.join(", ");
|
|
190
|
+
const forcedNotNeeded = input.config.allowedMockStrategies.length === 1 &&
|
|
191
|
+
input.config.allowedMockStrategies[0] === "not-needed";
|
|
192
|
+
const reason = forcedNotNeeded
|
|
193
|
+
? "the task is auto/not-required or lacks authorized deterministic Mock verification commands"
|
|
194
|
+
: `allowed strategies are [${quotedAllowed}]`;
|
|
195
|
+
throw new Error(`frontend contract mock strategy "${contract.mockApi.strategy}" is incompatible with the generated allowed strategies [${quotedAllowed}]; ${reason}. Regenerate the DAG or set frontendMock.policy=required with authorized Mock verification commands.`);
|
|
188
196
|
}
|
|
189
197
|
// Fail early instead of at the verification trace: a non-not-needed strategy
|
|
190
198
|
// can only be proven by executing the DAG's frozen Mock verification
|
|
@@ -2562,6 +2562,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2562
2562
|
allowedPaths: readOnlyPaths,
|
|
2563
2563
|
forbiddenPaths,
|
|
2564
2564
|
skills: FRONTEND_DESIGN_REVIEW_SKILLS,
|
|
2565
|
+
outputProtocol: REVIEW_VERDICT_OUTPUT_PROTOCOL,
|
|
2565
2566
|
outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings, Required Plan Corrections, and Checked Items. No file writes.",
|
|
2566
2567
|
subtask_prompt: [
|
|
2567
2568
|
"Audit the frontend plan before implementation.",
|
|
@@ -2618,6 +2619,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2618
2619
|
allowedPaths: readOnlyPaths,
|
|
2619
2620
|
forbiddenPaths,
|
|
2620
2621
|
skills: FRONTEND_DESIGN_REVIEW_SKILLS,
|
|
2622
|
+
outputProtocol: REVIEW_VERDICT_OUTPUT_PROTOCOL,
|
|
2621
2623
|
outputContract: "For the effective frontend plan, return plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings and Checked Items. No file writes.",
|
|
2622
2624
|
subtask_prompt: [
|
|
2623
2625
|
"Audit the revised frontend plan before implementation. This node runs only after request-revision and consumes frontend-plan-revision-pi.",
|