@tea-agent/loop-agent 0.35.1-beta.0 → 0.35.1-beta.2
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 +110 -108
- package/CHANGELOG.md +24 -26
- package/README.md +165 -165
- package/bin/agent-worker.js +0 -0
- package/bin/loop-agent.js +57 -21
- package/dist/application/task-lifecycle/advance.js +0 -1
- package/dist/build-stamp.json +6 -0
- package/dist/cli/program.js +2 -2
- package/dist/commands/cursor-prompt.js +6 -6
- package/dist/commands/init-upgrade.js +19 -351
- package/dist/commands/init.js +67 -14
- package/dist/commands/loop-benchmark.js +11 -11
- package/dist/commands/pi-reuse-benchmark.js +16 -16
- package/dist/commands/run-dag-progress.js +0 -14
- package/dist/commands/task-advance.js +3 -33
- package/dist/executors/dag-pi-executor.js +44 -0
- package/dist/shared/operator/capabilities.js +1 -38
- package/dist/shared/package-metadata.js +42 -0
- package/dist/sidecars/cursor-prompt/executor.js +1 -1
- package/dist/worker/console/chat/pi-runtime.js +25 -41
- package/dist/worker/console/chat/routes.js +4 -27
- package/dist/worker/console/operation-runner.js +0 -24
- package/dist/worker/console/operator-actions.js +0 -58
- package/dist/worker/console/static/assets/index-CvsQgALl.js +56 -0
- package/dist/worker/console/static/assets/{index-Dups4sSM.css → index-hJqCPs_g.css} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -13
- package/dist/worker/console/static-src/operator-chat/useComposer.js +7 -30
- package/dist/worker/loop-agent/loop-agent-client.js +17 -3
- package/dist/worker/observability/read-model.js +20 -0
- 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/worker/preflight.js +2 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +33 -23
- package/dist/workflows/dag/canvas-observer.js +275 -275
- package/dist/workflows/dag/contract-output-registry.js +14 -0
- package/dist/workflows/dag/contract-validator-registrations.js +8 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +9 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +233 -39
- package/dist/workflows/dag/frontend-prewrite-gate.js +364 -61
- package/dist/workflows/dag/frontend-recovery-plan.js +73 -0
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +123 -0
- package/dist/workflows/dag/frontend-recovery-run.js +539 -0
- package/dist/workflows/dag/frontend-repair.js +219 -18
- package/dist/workflows/dag/frontend-verification-trace.js +47 -32
- package/dist/workflows/dag/frontend-writer-recovery.js +106 -0
- package/dist/workflows/dag/frontend-writer-rollback.js +821 -0
- package/dist/workflows/dag/init-hybrid.js +41 -24
- package/dist/workflows/dag/node-execution.js +89 -0
- package/dist/workflows/dag/recovery-recommendation.js +58 -0
- package/dist/workflows/dag/runner.js +245 -11
- package/dist/workflows/dag/scheduler.js +257 -3
- package/dist/workflows/dag/types.js +130 -2
- 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 +154 -156
- 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 +2 -5
- package/package.json +2 -2
- 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 +0 -1
- package/skills/loop-agent/references/command-reference.md +639 -641
- 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/operation-wait.js +0 -241
- package/dist/worker/console/static/assets/index-SjjjZnV3.js +0 -56
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
2
2
|
import { filterSlashCommands, mergeSlashCommands, } from "../../chat/shortcuts.js";
|
|
3
3
|
import { confirmationToken } from "./format.js";
|
|
4
|
-
import { clampShortcutIndex } from "./slash-palette-nav.js";
|
|
5
4
|
import { attachRuntimeSnapshotController } from "./useRuntimeSnapshot.js";
|
|
6
5
|
/** Composer state: input text, @file palette, image attachments, /shortcuts,
|
|
7
6
|
* IME composition guards, auto-grow and server-side draft sync. */
|
|
@@ -17,22 +16,12 @@ export function useComposer(params) {
|
|
|
17
16
|
const [dynamicCommandsError, setDynamicCommandsError] = useState(null);
|
|
18
17
|
const [commandsEpoch, setCommandsEpoch] = useState(0);
|
|
19
18
|
const textareaRef = useRef(null);
|
|
20
|
-
|
|
21
|
-
const commandsSessionRef = useRef(sessionId);
|
|
22
|
-
const commandsGenerationRef = useRef(0);
|
|
19
|
+
const commandsSessionRef = useRef(undefined);
|
|
23
20
|
const handleInputChange = useCallback((value) => {
|
|
24
21
|
setInput(value);
|
|
25
22
|
setShortcutOpen(value.startsWith("/"));
|
|
26
23
|
setShortcutIndex(0);
|
|
27
24
|
}, []);
|
|
28
|
-
// Immediate invalidation on A→B even when B palette never opens (AC-2).
|
|
29
|
-
useLayoutEffect(() => {
|
|
30
|
-
commandsSessionRef.current = sessionId;
|
|
31
|
-
commandsGenerationRef.current += 1;
|
|
32
|
-
setDynamicCommands([]);
|
|
33
|
-
setDynamicCommandsError(null);
|
|
34
|
-
setDynamicCommandsLoading(false);
|
|
35
|
-
}, [sessionId]);
|
|
36
25
|
const loadDynamicCommands = useCallback(async () => {
|
|
37
26
|
if (!sessionId) {
|
|
38
27
|
setDynamicCommands([]);
|
|
@@ -41,15 +30,12 @@ export function useComposer(params) {
|
|
|
41
30
|
return;
|
|
42
31
|
}
|
|
43
32
|
const target = sessionId;
|
|
44
|
-
const generation = commandsGenerationRef.current;
|
|
45
33
|
commandsSessionRef.current = target;
|
|
46
34
|
setDynamicCommandsLoading(true);
|
|
47
35
|
setDynamicCommandsError(null);
|
|
48
|
-
const isCurrent = () => commandsSessionRef.current === target &&
|
|
49
|
-
commandsGenerationRef.current === generation;
|
|
50
36
|
try {
|
|
51
37
|
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(target)}/commands`, { credentials: "include" });
|
|
52
|
-
if (
|
|
38
|
+
if (commandsSessionRef.current !== target)
|
|
53
39
|
return;
|
|
54
40
|
if (!res.ok) {
|
|
55
41
|
setDynamicCommandsError("动态命令加载失败");
|
|
@@ -75,27 +61,26 @@ export function useComposer(params) {
|
|
|
75
61
|
source,
|
|
76
62
|
});
|
|
77
63
|
}
|
|
78
|
-
if (
|
|
64
|
+
if (commandsSessionRef.current !== target)
|
|
79
65
|
return;
|
|
80
66
|
setDynamicCommands(items);
|
|
81
|
-
// Compact server fail-soft / partial-source warning; local Operator/Web still merge.
|
|
82
|
-
const warning = typeof body?.warning === "string" ? body.warning.trim() : "";
|
|
83
|
-
setDynamicCommandsError(warning ? warning.slice(0, 160) : null);
|
|
84
67
|
}
|
|
85
68
|
catch {
|
|
86
|
-
if (
|
|
69
|
+
if (commandsSessionRef.current !== target)
|
|
87
70
|
return;
|
|
88
71
|
setDynamicCommandsError("动态命令加载失败");
|
|
89
72
|
setDynamicCommands([]);
|
|
90
73
|
}
|
|
91
74
|
finally {
|
|
92
|
-
if (
|
|
75
|
+
if (commandsSessionRef.current === target) {
|
|
93
76
|
setDynamicCommandsLoading(false);
|
|
94
77
|
}
|
|
95
78
|
}
|
|
96
79
|
}, [origin, sessionId]);
|
|
97
80
|
// Lazy-load dynamic commands when slash palette opens or session changes.
|
|
98
81
|
useEffect(() => {
|
|
82
|
+
setDynamicCommands([]);
|
|
83
|
+
setDynamicCommandsError(null);
|
|
99
84
|
if (!sessionId)
|
|
100
85
|
return;
|
|
101
86
|
if (!shortcutOpen && commandsEpoch === 0)
|
|
@@ -187,14 +172,6 @@ export function useComposer(params) {
|
|
|
187
172
|
const filteredShortcuts = useMemo(() => input.startsWith("/")
|
|
188
173
|
? filterSlashCommands(mergedCommands, input)
|
|
189
174
|
: [], [input, mergedCommands]);
|
|
190
|
-
// Keep the active option index inside the current candidate range (UI-12
|
|
191
|
-
// AC-011): dynamic loads completing/failing empty, retries, session
|
|
192
|
-
// switches, late responses and search shrink all funnel through
|
|
193
|
-
// filteredShortcuts.length. The clamped index always matches a rendered
|
|
194
|
-
// `oc-shortcut-option-${globalIndex}` id (same source of truth).
|
|
195
|
-
useEffect(() => {
|
|
196
|
-
setShortcutIndex((index) => clampShortcutIndex(index, filteredShortcuts.length));
|
|
197
|
-
}, [filteredShortcuts.length]);
|
|
198
175
|
const onCompositionStart = useCallback(() => {
|
|
199
176
|
refs.isComposingRef.current = true;
|
|
200
177
|
}, [refs]);
|
|
@@ -2,11 +2,15 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { appendFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { computeBinarySha256, computePackageFingerprint, findPackageRoot, isJavaScriptScript, readPackageName, readPackageVersion, resolveBinPaths, resolveCmdShim, } from "../../shared/package-metadata.js";
|
|
5
|
+
import { computeBinarySha256, computePackageFingerprint, findPackageRoot, isJavaScriptScript, normalizeCliVersionLabel, readPackageName, readPackageVersion, resolveBinPaths, resolveCmdShim, } from "../../shared/package-metadata.js";
|
|
6
6
|
import { parseCommandJson } from "./parse-json.js";
|
|
7
7
|
export const DEFAULT_WORKER_COMMAND_TIMEOUT_MS = 120_000;
|
|
8
8
|
export const DEFAULT_HEARTBEAT_INTERVAL_MS = 15_000;
|
|
9
9
|
export const LOOP_AGENT_PACKAGE_NAME = "@tea-agent/loop-agent";
|
|
10
|
+
/** Grace window between the SIGTERM and SIGKILL stages of a hard timeout, so a
|
|
11
|
+
* runner can persist terminal state (see executeDagCheckpoint) before being
|
|
12
|
+
* force-killed. */
|
|
13
|
+
export const HARD_TIMEOUT_KILL_GRACE_MS = 10_000;
|
|
10
14
|
const MAX_BUFFERED_OUTPUT_BYTES = 64 * 1024;
|
|
11
15
|
/**
|
|
12
16
|
* Bind evidence supplied by an orchestration layer to the identity exposed by
|
|
@@ -178,10 +182,11 @@ export class LoopAgentClient {
|
|
|
178
182
|
if (!this._identity) {
|
|
179
183
|
throw new Error("controller identity: cannot record a reported version without a pinned identity");
|
|
180
184
|
}
|
|
181
|
-
|
|
185
|
+
const normalized = normalizeCliVersionLabel(reportedVersion);
|
|
186
|
+
if (normalized !== this._identity.packageVersion) {
|
|
182
187
|
throw new Error(`controller identity: reported version ${reportedVersion} does not match package version ${this._identity.packageVersion}`);
|
|
183
188
|
}
|
|
184
|
-
this._identity = { ...this._identity, reportedVersion };
|
|
189
|
+
this._identity = { ...this._identity, reportedVersion: normalized };
|
|
185
190
|
return this._identity;
|
|
186
191
|
}
|
|
187
192
|
async run(args, options) {
|
|
@@ -381,10 +386,17 @@ function spawnCommand(input) {
|
|
|
381
386
|
});
|
|
382
387
|
};
|
|
383
388
|
let hardTimeout;
|
|
389
|
+
let killGraceTimeout;
|
|
384
390
|
if (input.hardTimeoutMs !== undefined && input.hardTimeoutMs > 0) {
|
|
385
391
|
hardTimeout = setTimeout(() => {
|
|
386
392
|
timedOut = true;
|
|
393
|
+
// Two-stage termination: SIGTERM first so the runner can persist a
|
|
394
|
+
// terminal state (see executeDagCheckpoint signal handler), then
|
|
395
|
+
// SIGKILL only after the grace window expires.
|
|
387
396
|
child.kill("SIGTERM");
|
|
397
|
+
killGraceTimeout = setTimeout(() => {
|
|
398
|
+
child.kill("SIGKILL");
|
|
399
|
+
}, HARD_TIMEOUT_KILL_GRACE_MS);
|
|
388
400
|
}, input.hardTimeoutMs);
|
|
389
401
|
}
|
|
390
402
|
let heartbeatTimer;
|
|
@@ -394,6 +406,8 @@ function spawnCommand(input) {
|
|
|
394
406
|
const clearTimers = () => {
|
|
395
407
|
if (hardTimeout !== undefined)
|
|
396
408
|
clearTimeout(hardTimeout);
|
|
409
|
+
if (killGraceTimeout !== undefined)
|
|
410
|
+
clearTimeout(killGraceTimeout);
|
|
397
411
|
if (heartbeatTimer !== undefined) {
|
|
398
412
|
clearInterval(heartbeatTimer);
|
|
399
413
|
heartbeatTimer = undefined;
|
|
@@ -1491,6 +1491,7 @@ function mergeDagRun(existing, incoming) {
|
|
|
1491
1491
|
failureCategory: incoming.failureCategory ?? existing.failureCategory,
|
|
1492
1492
|
stateConsistent: incoming.stateConsistent ?? existing.stateConsistent,
|
|
1493
1493
|
recoveryEligibility: incoming.recoveryEligibility ?? existing.recoveryEligibility,
|
|
1494
|
+
frontendRecovery: incoming.frontendRecovery ?? existing.frontendRecovery,
|
|
1494
1495
|
title: incoming.title ?? existing.title,
|
|
1495
1496
|
startedAt,
|
|
1496
1497
|
finishedAt,
|
|
@@ -1790,6 +1791,23 @@ async function loadFrontendLintProjection(runDir) {
|
|
|
1790
1791
|
blockingReasons: parsed.data.blockingReasons,
|
|
1791
1792
|
};
|
|
1792
1793
|
}
|
|
1794
|
+
function projectFrontendRecovery(state) {
|
|
1795
|
+
const s = state.frontendRecoveryState;
|
|
1796
|
+
const r = state.frontendRecoveryResult;
|
|
1797
|
+
if (!s && !r)
|
|
1798
|
+
return undefined;
|
|
1799
|
+
return {
|
|
1800
|
+
...(s?.recoveryRootRunId ? { recoveryRootRunId: s.recoveryRootRunId } : {}),
|
|
1801
|
+
...(s?.parentRunId ? { parentRunId: s.parentRunId } : {}),
|
|
1802
|
+
...(s?.childRunId ? { childRunId: s.childRunId } : {}),
|
|
1803
|
+
...(s?.attemptIndex !== undefined ? { attemptIndex: s.attemptIndex } : {}),
|
|
1804
|
+
...(s?.continuationCount !== undefined
|
|
1805
|
+
? { continuationCount: s.continuationCount }
|
|
1806
|
+
: {}),
|
|
1807
|
+
...(s?.phase ? { phase: s.phase } : {}),
|
|
1808
|
+
...(r?.outcome ? { outcome: r.outcome } : {}),
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1793
1811
|
async function parseDagStateFile(statePath, now) {
|
|
1794
1812
|
try {
|
|
1795
1813
|
if (!existsSync(statePath))
|
|
@@ -1860,6 +1878,7 @@ async function parseDagStateFile(statePath, now) {
|
|
|
1860
1878
|
loadBackendTestProjection(runDir, nodes),
|
|
1861
1879
|
loadFrontendLintProjection(runDir),
|
|
1862
1880
|
]);
|
|
1881
|
+
const frontendRecovery = projectFrontendRecovery(state);
|
|
1863
1882
|
const continuationRecord = state.continuation &&
|
|
1864
1883
|
typeof state.continuation.parentRunId === "string" &&
|
|
1865
1884
|
typeof state.continuation.effectiveFromNodeId === "string" &&
|
|
@@ -1918,6 +1937,7 @@ async function parseDagStateFile(statePath, now) {
|
|
|
1918
1937
|
...(continuation ? { continuation } : {}),
|
|
1919
1938
|
...(backendTest ? { backendTest } : {}),
|
|
1920
1939
|
...(frontendLint ? { frontendLint } : {}),
|
|
1940
|
+
...(frontendRecovery ? { frontendRecovery } : {}),
|
|
1921
1941
|
};
|
|
1922
1942
|
}
|
|
1923
1943
|
catch {
|
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
/** Copy-only recommended commands (Observe UI R5). */
|
|
2
|
-
import { el } from "./dom.js";
|
|
3
|
-
|
|
4
|
-
export async function copyText(text) {
|
|
5
|
-
const value = String(text ?? "");
|
|
6
|
-
if (!value) return false;
|
|
7
|
-
try {
|
|
8
|
-
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
9
|
-
await navigator.clipboard.writeText(value);
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
} catch {
|
|
13
|
-
// fall through
|
|
14
|
-
}
|
|
15
|
-
try {
|
|
16
|
-
const ta = document.createElement("textarea");
|
|
17
|
-
ta.value = value;
|
|
18
|
-
ta.setAttribute("readonly", "");
|
|
19
|
-
ta.style.position = "fixed";
|
|
20
|
-
ta.style.left = "-9999px";
|
|
21
|
-
document.body.appendChild(ta);
|
|
22
|
-
ta.select();
|
|
23
|
-
const ok = document.execCommand("copy");
|
|
24
|
-
document.body.removeChild(ta);
|
|
25
|
-
return ok;
|
|
26
|
-
} catch {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Advisory recommended command: copy-only, never executes. */
|
|
32
|
-
export function renderRecommendedCommand(action) {
|
|
33
|
-
const block = el("div", "recommended-command");
|
|
34
|
-
if (!action) {
|
|
35
|
-
block.appendChild(el("span", "muted", "缺失"));
|
|
36
|
-
return block;
|
|
37
|
-
}
|
|
38
|
-
const head = el("div", "recommended-command-head");
|
|
39
|
-
if (action.kind) {
|
|
40
|
-
head.appendChild(el("span", "recommended-command-kind", action.kind));
|
|
41
|
-
}
|
|
42
|
-
head.appendChild(
|
|
43
|
-
el("span", "recommended-command-label", action.label ?? "—"),
|
|
44
|
-
);
|
|
45
|
-
block.appendChild(head);
|
|
46
|
-
if (action.command) {
|
|
47
|
-
const body = el("div", "recommended-command-body");
|
|
48
|
-
const code = el("code", "recommended-command-text", action.command);
|
|
49
|
-
body.appendChild(code);
|
|
50
|
-
const btn = el("button", "copy-command", "复制");
|
|
51
|
-
btn.type = "button";
|
|
52
|
-
btn.addEventListener("click", (e) => {
|
|
53
|
-
e.preventDefault();
|
|
54
|
-
e.stopPropagation();
|
|
55
|
-
void copyText(action.command).then((ok) => {
|
|
56
|
-
btn.textContent = ok ? "已复制" : "复制失败";
|
|
57
|
-
setTimeout(() => {
|
|
58
|
-
btn.textContent = "复制";
|
|
59
|
-
}, 1500);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
body.appendChild(btn);
|
|
63
|
-
block.appendChild(body);
|
|
64
|
-
}
|
|
65
|
-
return block;
|
|
66
|
-
}
|
|
67
|
-
|
|
1
|
+
/** Copy-only recommended commands (Observe UI R5). */
|
|
2
|
+
import { el } from "./dom.js";
|
|
3
|
+
|
|
4
|
+
export async function copyText(text) {
|
|
5
|
+
const value = String(text ?? "");
|
|
6
|
+
if (!value) return false;
|
|
7
|
+
try {
|
|
8
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
9
|
+
await navigator.clipboard.writeText(value);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
} catch {
|
|
13
|
+
// fall through
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const ta = document.createElement("textarea");
|
|
17
|
+
ta.value = value;
|
|
18
|
+
ta.setAttribute("readonly", "");
|
|
19
|
+
ta.style.position = "fixed";
|
|
20
|
+
ta.style.left = "-9999px";
|
|
21
|
+
document.body.appendChild(ta);
|
|
22
|
+
ta.select();
|
|
23
|
+
const ok = document.execCommand("copy");
|
|
24
|
+
document.body.removeChild(ta);
|
|
25
|
+
return ok;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Advisory recommended command: copy-only, never executes. */
|
|
32
|
+
export function renderRecommendedCommand(action) {
|
|
33
|
+
const block = el("div", "recommended-command");
|
|
34
|
+
if (!action) {
|
|
35
|
+
block.appendChild(el("span", "muted", "缺失"));
|
|
36
|
+
return block;
|
|
37
|
+
}
|
|
38
|
+
const head = el("div", "recommended-command-head");
|
|
39
|
+
if (action.kind) {
|
|
40
|
+
head.appendChild(el("span", "recommended-command-kind", action.kind));
|
|
41
|
+
}
|
|
42
|
+
head.appendChild(
|
|
43
|
+
el("span", "recommended-command-label", action.label ?? "—"),
|
|
44
|
+
);
|
|
45
|
+
block.appendChild(head);
|
|
46
|
+
if (action.command) {
|
|
47
|
+
const body = el("div", "recommended-command-body");
|
|
48
|
+
const code = el("code", "recommended-command-text", action.command);
|
|
49
|
+
body.appendChild(code);
|
|
50
|
+
const btn = el("button", "copy-command", "复制");
|
|
51
|
+
btn.type = "button";
|
|
52
|
+
btn.addEventListener("click", (e) => {
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
e.stopPropagation();
|
|
55
|
+
void copyText(action.command).then((ok) => {
|
|
56
|
+
btn.textContent = ok ? "已复制" : "复制失败";
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
btn.textContent = "复制";
|
|
59
|
+
}, 1500);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
body.appendChild(btn);
|
|
63
|
+
block.appendChild(body);
|
|
64
|
+
}
|
|
65
|
+
return block;
|
|
66
|
+
}
|
|
67
|
+
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
export type DagLayoutNodeInput = { nodeId: string };
|
|
2
|
-
export type DagLayoutEdgeInput = { from: string; to: string };
|
|
3
|
-
export type DagLayoutNode = {
|
|
4
|
-
nodeId: string;
|
|
5
|
-
rank: number;
|
|
6
|
-
index: number;
|
|
7
|
-
x: number;
|
|
8
|
-
y: number;
|
|
9
|
-
width: number;
|
|
10
|
-
height: number;
|
|
11
|
-
};
|
|
12
|
-
export type DagLayoutEdge = DagLayoutEdgeInput & { path: string };
|
|
13
|
-
export type DagLayout = {
|
|
14
|
-
width: number;
|
|
15
|
-
height: number;
|
|
16
|
-
nodes: DagLayoutNode[];
|
|
17
|
-
edges: DagLayoutEdge[];
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export type DagLayoutOptions = Partial<{
|
|
21
|
-
nodeWidth: number;
|
|
22
|
-
nodeHeight: number;
|
|
23
|
-
rankGap: number;
|
|
24
|
-
nodeGap: number;
|
|
25
|
-
padding: number;
|
|
26
|
-
controlLaneCount: number;
|
|
27
|
-
/** Per-node visual card height overrides; ordinary ports still use nodeHeight axis. */
|
|
28
|
-
nodeHeights: Record<string, number>;
|
|
29
|
-
}>;
|
|
30
|
-
|
|
31
|
-
export function layoutDag(
|
|
32
|
-
nodes?: DagLayoutNodeInput[],
|
|
33
|
-
edges?: DagLayoutEdgeInput[],
|
|
34
|
-
ranks?: string[][],
|
|
35
|
-
options?: DagLayoutOptions,
|
|
36
|
-
): DagLayout;
|
|
1
|
+
export type DagLayoutNodeInput = { nodeId: string };
|
|
2
|
+
export type DagLayoutEdgeInput = { from: string; to: string };
|
|
3
|
+
export type DagLayoutNode = {
|
|
4
|
+
nodeId: string;
|
|
5
|
+
rank: number;
|
|
6
|
+
index: number;
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
};
|
|
12
|
+
export type DagLayoutEdge = DagLayoutEdgeInput & { path: string };
|
|
13
|
+
export type DagLayout = {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
nodes: DagLayoutNode[];
|
|
17
|
+
edges: DagLayoutEdge[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type DagLayoutOptions = Partial<{
|
|
21
|
+
nodeWidth: number;
|
|
22
|
+
nodeHeight: number;
|
|
23
|
+
rankGap: number;
|
|
24
|
+
nodeGap: number;
|
|
25
|
+
padding: number;
|
|
26
|
+
controlLaneCount: number;
|
|
27
|
+
/** Per-node visual card height overrides; ordinary ports still use nodeHeight axis. */
|
|
28
|
+
nodeHeights: Record<string, number>;
|
|
29
|
+
}>;
|
|
30
|
+
|
|
31
|
+
export function layoutDag(
|
|
32
|
+
nodes?: DagLayoutNodeInput[],
|
|
33
|
+
edges?: DagLayoutEdgeInput[],
|
|
34
|
+
ranks?: string[][],
|
|
35
|
+
options?: DagLayoutOptions,
|
|
36
|
+
): DagLayout;
|