@tea-agent/loop-agent 0.10.0 → 0.12.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 +10 -2
- package/CHANGELOG.md +91 -24
- package/README.md +84 -12
- package/dist/application/dag/args.js +1 -12
- package/dist/application/dag/generate-task-dag.js +38 -2
- package/dist/application/dag/run-dag.js +11 -27
- package/dist/application/dag/validate-dag.js +13 -2
- package/dist/application/loop/run-action.js +0 -4
- package/dist/cli/command-definitions.js +44 -16
- package/dist/cli/program.js +40 -23
- package/dist/cli/update/notifier.js +117 -0
- package/dist/cli/update/npm-client.js +151 -0
- package/dist/cli/update/policy.js +58 -0
- package/dist/cli/update/state.js +68 -0
- package/dist/cli.js +33 -0
- package/dist/commands/cursor-prompt.js +42 -82
- package/dist/commands/dag-approve.js +36 -0
- package/dist/commands/delegate.js +75 -77
- package/dist/commands/doctor.js +0 -18
- package/dist/commands/init.js +547 -95
- package/dist/commands/instructions.js +7 -10
- package/dist/commands/loop.js +4 -20
- package/dist/commands/plan.js +50 -0
- package/dist/executors/config-core.js +0 -51
- package/dist/executors/dag-pi-executor.js +1 -1
- package/dist/executors/dag.js +0 -1
- package/dist/executors/index.js +0 -2
- package/dist/executors/model-routing.js +9 -9
- package/dist/executors/shell-executor.js +1 -1
- package/dist/governance/checks.js +6 -3
- package/dist/governance/exec-plans.js +545 -0
- package/dist/governance/manifest-types.js +24 -2
- package/dist/infrastructure/harness/loop-action-store.js +0 -3
- package/dist/records/harvest.js +2 -23
- package/dist/records/one-shot-runs.js +1 -1
- package/dist/shared/artifacts-core.js +24 -5
- package/dist/shared/output-truncation.js +37 -0
- package/dist/shared/package-metadata.js +488 -0
- package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
- package/dist/sidecars/cursor-prompt/index.js +3 -0
- package/dist/sidecars/cursor-prompt/stream.js +121 -0
- package/dist/task/config-types.js +29 -12
- package/dist/task/delegate.js +9 -21
- package/dist/task/runtime.js +1 -2
- package/dist/worker/cli.js +32 -3
- package/dist/worker/delivery/final-verification.js +47 -11
- package/dist/worker/delivery/package.js +63 -10
- package/dist/worker/feature/run.js +60 -8
- package/dist/worker/loop-agent/loop-agent-client.js +329 -126
- package/dist/worker/observability/event-history.js +216 -0
- package/dist/worker/observability/read-model.js +338 -83
- package/dist/worker/observe/paths.js +17 -0
- package/dist/worker/observe/routes.js +165 -21
- package/dist/worker/observe/server.js +59 -1
- package/dist/worker/observe/static/api.js +27 -0
- package/dist/worker/observe/static/app.js +120 -2317
- package/dist/worker/observe/static/constants.js +148 -0
- package/dist/worker/observe/static/copy.js +67 -0
- package/dist/worker/observe/static/dag-helpers.js +172 -0
- package/dist/worker/observe/static/dag-model.js +72 -0
- package/dist/worker/observe/static/dom.js +61 -0
- package/dist/worker/observe/static/format-pool.js +67 -0
- package/dist/worker/observe/static/format.js +292 -0
- package/dist/worker/observe/static/index.html +300 -82
- package/dist/worker/observe/static/kpi.js +94 -0
- package/dist/worker/observe/static/relations.js +128 -0
- package/dist/worker/observe/static/router.js +85 -0
- package/dist/worker/observe/static/run-processing.js +148 -0
- package/dist/worker/observe/static/shell-chrome.js +68 -0
- package/dist/worker/observe/static/state.js +253 -0
- package/dist/worker/observe/static/styles.css +1720 -495
- package/dist/worker/observe/static/views/batch.js +226 -0
- package/dist/worker/observe/static/views/dag-graph.js +172 -0
- package/dist/worker/observe/static/views/dag-inspector.js +477 -0
- package/dist/worker/observe/static/views/dag.js +362 -0
- package/dist/worker/observe/static/views/dashboard.js +442 -0
- package/dist/worker/observe/static/views/failures.js +143 -0
- package/dist/worker/observe/static/views/feature.js +453 -0
- package/dist/worker/observe/static/views/pool.js +347 -0
- package/dist/worker/observe/static/views/run.js +453 -0
- package/dist/worker/observe/static/views/session-timeline.js +205 -0
- package/dist/worker/observe/static/views/shell.js +7 -0
- package/dist/worker/observe/static/views/task.js +260 -0
- package/dist/worker/observe/static/views/timeline.js +163 -0
- package/dist/worker/preflight.js +49 -1
- package/dist/worker/run-task/run-task.js +22 -12
- package/dist/worker/runner/run-ready.js +76 -12
- package/dist/worker/task-spec/schema.js +0 -1
- package/dist/workflows/dag/controller-identity.js +104 -0
- package/dist/workflows/dag/convergence/controller.js +1 -1
- package/dist/workflows/dag/executor-registry.js +0 -2
- package/dist/workflows/dag/init-hybrid.js +797 -27
- package/dist/workflows/dag/node-execution.js +183 -35
- package/dist/workflows/dag/repair-artifact.js +91 -0
- package/dist/workflows/dag/report.js +50 -0
- package/dist/workflows/dag/retry-policy.js +138 -0
- package/dist/workflows/dag/runner.js +77 -17
- package/dist/workflows/dag/runtime-contract.js +87 -0
- package/dist/workflows/dag/scheduler.js +7 -2
- package/dist/workflows/dag/sdd-embedded.js +128 -0
- package/dist/workflows/dag/skill-instructions.js +5 -4
- package/dist/workflows/dag/skill-snapshot.js +529 -0
- package/dist/workflows/dag/types.js +86 -10
- package/dist/workflows/dag/validate.js +73 -12
- package/dist/workflows/loop/actions/dag-action.js +0 -2
- package/dist/workflows/loop/actions/shared.js +1 -1
- package/dist/workflows/loop/actions.js +14 -31
- package/dist/workflows/loop/benchmark.js +1 -1
- package/dist/workflows/loop/index.js +1 -1
- package/dist/workflows/loop/policy/auto-policy.js +22 -14
- package/dist/workflows/loop/policy/path-patterns.js +13 -0
- package/docs/README.md +36 -33
- package/docs/agent-dag-recovery-playbook.md +1 -1
- package/docs/agent-dag-runner.md +28 -3
- package/docs/architecture/README.md +26 -0
- package/docs/architecture/dag-execution.md +140 -0
- package/docs/architecture/evolution.md +53 -0
- package/docs/architecture/facts-and-state.md +58 -0
- package/docs/architecture/runtime-boundaries.md +45 -17
- package/docs/architecture/system-overview.md +93 -0
- package/docs/architecture/worker-and-feature.md +81 -0
- package/docs/cursor-prompt-sidecar.md +36 -0
- package/docs/decisions/README.md +13 -1
- package/docs/design/README.md +43 -21
- package/docs/development-principles.md +2 -2
- package/docs/exec-plans/active/README.md +1 -3
- package/docs/exec-plans/completed/README.md +23 -0
- package/docs/feature-workflow.md +78 -4
- package/docs/harness-methodology-debugging.md +1 -1
- package/docs/harness-methodology-tdd.md +3 -3
- package/docs/init-surface.manifest.json +60 -25
- package/docs/loop-agent-harness.md +28 -4
- package/docs/progress/README.md +50 -1
- package/docs/reports/README.md +90 -18
- package/docs/skills/README.md +2 -1
- package/docs/skills/vetted-skill-registry.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +23 -6
- package/docs/templates/agent-dag.base.json +0 -5
- package/docs/templates/agent-dag.final-verification.json +0 -5
- package/docs/templates/agent-dag.schema.json +70 -3
- package/docs/templates/agent-dag.supervised-implementation.json +9 -8
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +139 -0
- package/docs/templates/backend-test-dag.json +276 -0
- package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -0
- package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -0
- package/docs/templates/frontend-design-contract.md +33 -0
- package/docs/templates/frontend-task-constraints.md +25 -0
- package/docs/templates/frontend-task-requirement.md +61 -0
- package/docs/templates/harness.schema.json +10 -12
- package/docs/templates/hybrid-dag.json +1 -6
- package/docs/templates/interactive-ui-round2-experiment.md +1 -1
- package/docs/templates/product-line/task.yaml +0 -1
- package/docs/templates/project-start-checklist.md +2 -2
- package/docs/templates/worker-dogfood-evidence.md +28 -0
- package/docs/templates/worker-dogfood-setup.md +20 -0
- package/docs/verification-matrix.md +10 -0
- package/examples/decision-gate-agent-dag.json +87 -33
- package/examples/example-dag.json +0 -5
- package/examples/hybrid-loop-agent-dag.json +0 -5
- package/harness.json +7 -15
- package/package.json +22 -46
- package/scripts/check-product-line-docs.sh +10 -7
- package/skills/agent-worker/SKILL.md +37 -0
- package/skills/agent-worker/references/agent-worker-operator.md +43 -0
- package/skills/frontend-design-review/SKILL.md +59 -0
- package/skills/frontend-design-review/references/review-checklist.md +37 -0
- package/skills/frontend-implementation/SKILL.md +51 -0
- package/skills/frontend-implementation/references/code-standards.md +34 -0
- package/skills/frontend-implementation/references/design-spec.md +46 -0
- package/skills/frontend-implementation/references/node-contracts.md +32 -0
- package/skills/frontend-review/SKILL.md +53 -0
- package/skills/frontend-review/references/review-findings.md +42 -0
- package/skills/frontend-verification/SKILL.md +40 -0
- package/skills/frontend-verification/references/verification-checklist.md +56 -0
- package/skills/grill-me/SKILL.md +10 -0
- package/skills/grill-with-docs/SKILL.md +88 -0
- package/skills/grill-with-docs/adr-format.md +47 -0
- package/skills/grill-with-docs/context-format.md +60 -0
- package/skills/loop-agent/SKILL.md +11 -9
- package/skills/loop-agent/references/command-reference.md +14 -15
- package/skills/loop-agent/references/docs-converge.md +126 -0
- package/skills/loop-agent/references/harness-policy.md +7 -7
- package/skills/loop-agent/references/hybrid-dag.md +36 -20
- package/skills/loop-agent/references/long-running-loop.md +4 -6
- package/skills/loop-agent/references/multi-worktree.md +6 -6
- package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
- package/skills/loop-agent/references/task-workflow.md +1 -1
- package/skills/loop-agent/references/verification-and-failure-handling.md +6 -0
- package/skills/using-git-worktrees/SKILL.md +215 -0
- package/dist/commands/cursor-worker.js +0 -43
- package/dist/cursor-worker-entry.js +0 -8
- package/dist/executors/cursor-artifacts.js +0 -33
- package/dist/executors/cursor-execution-log.js +0 -81
- package/dist/executors/cursor-executor-artifacts.js +0 -134
- package/dist/executors/cursor-run.js +0 -115
- package/dist/executors/cursor-tool.js +0 -94
- package/dist/executors/cursor-worker-client.js +0 -223
- package/dist/executors/cursor-worker-protocol.js +0 -18
- package/dist/executors/cursor-worker-server.js +0 -54
- package/dist/executors/cursor-worker.js +0 -3
- package/dist/executors/cursor.js +0 -6
- package/dist/executors/dag-cursor-executor.js +0 -87
- package/dist/workflows/loop/actions/cursor-fix.js +0 -191
- package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
- package/docs/cursor-executor-usage.md +0 -25
- package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/** Observe UI view module (R5). */
|
|
2
|
+
import {
|
|
3
|
+
UI_TEXT,
|
|
4
|
+
LIVENESS_LABELS,
|
|
5
|
+
ARTIFACT_LABELS,
|
|
6
|
+
ARTIFACT_KEYS,
|
|
7
|
+
STEP_LABELS,
|
|
8
|
+
STEP_ORDER,
|
|
9
|
+
DAG_INSPECTOR_DEFAULT_WIDTH,
|
|
10
|
+
DAG_INSPECTOR_MIN_WIDTH,
|
|
11
|
+
DAG_INSPECTOR_MAX_WIDTH,
|
|
12
|
+
DAG_INSPECTOR_RIGHT_OFFSET,
|
|
13
|
+
DAG_INSPECTOR_VIEWPORT_MARGIN,
|
|
14
|
+
DAG_INSPECTOR_KEYBOARD_STEP,
|
|
15
|
+
TERMINAL_RUN_STATUSES,
|
|
16
|
+
DAG_EFFECTIVE_STATUS_LABELS,
|
|
17
|
+
STATUS_LABELS,
|
|
18
|
+
} from "../constants.js";
|
|
19
|
+
import { clearNode, el, metaGrid, buildTable } from "../dom.js";
|
|
20
|
+
import {
|
|
21
|
+
badge,
|
|
22
|
+
badgeClass,
|
|
23
|
+
statusLabel,
|
|
24
|
+
formatTs,
|
|
25
|
+
formatMs,
|
|
26
|
+
formatDuration,
|
|
27
|
+
formatAgo,
|
|
28
|
+
dagStatusBadge,
|
|
29
|
+
livenessBadge,
|
|
30
|
+
displayDurationMs,
|
|
31
|
+
derivedDurationMs,
|
|
32
|
+
summaryText,
|
|
33
|
+
truncateText,
|
|
34
|
+
repoBaseName,
|
|
35
|
+
parseMarkdownBlocks,
|
|
36
|
+
markdownLinkHref,
|
|
37
|
+
} from "../format.js";
|
|
38
|
+
import { parseRoute, navigate, buildHashPath } from "../router.js";
|
|
39
|
+
import { fetchJson, artifactUrl, parseArtifactPreviewResponse } from "../api.js";
|
|
40
|
+
import {
|
|
41
|
+
objectRouteLink,
|
|
42
|
+
findFeatureInSnapshot,
|
|
43
|
+
featureLookupState,
|
|
44
|
+
buildObjectRelations,
|
|
45
|
+
renderObjectRelationBar,
|
|
46
|
+
} from "../relations.js";
|
|
47
|
+
import { copyText, renderRecommendedCommand } from "../copy.js";
|
|
48
|
+
import {
|
|
49
|
+
isPageVisible,
|
|
50
|
+
detailPollDelay,
|
|
51
|
+
dashboardPollDelay,
|
|
52
|
+
captureDagGraphViewportState,
|
|
53
|
+
updateDagGraphViewportState,
|
|
54
|
+
updateDagTimelineViewportState,
|
|
55
|
+
renderViewState,
|
|
56
|
+
mountViewState,
|
|
57
|
+
} from "../state.js";
|
|
58
|
+
import { uiState } from "../state.js";
|
|
59
|
+
import { sortPoolTasks } from "../format-pool.js";
|
|
60
|
+
import {
|
|
61
|
+
dagMetaVisibility,
|
|
62
|
+
isDagRunActive,
|
|
63
|
+
dashboardVisibleFeatures,
|
|
64
|
+
findDagForRun,
|
|
65
|
+
} from "../dag-model.js";
|
|
66
|
+
import { layoutDag } from "../dag-layout.js";
|
|
67
|
+
import { showView, setBreadcrumb, updateHeaderRefresh, mountRelationBar } from "../shell-chrome.js";
|
|
68
|
+
import { artifactLink } from "../kpi.js";
|
|
69
|
+
import { findBatchRunIdForTask, taskRunHistoryRow } from "../run-processing.js";
|
|
70
|
+
|
|
71
|
+
export async function renderTaskDetail(taskId) {
|
|
72
|
+
showView("task");
|
|
73
|
+
setBreadcrumb([
|
|
74
|
+
{ label: UI_TEXT.dashboard, href: "#/" },
|
|
75
|
+
{ label: "资源池", href: "#/pool" },
|
|
76
|
+
{ label: taskId },
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const titleEl = document.getElementById("task-title");
|
|
80
|
+
const metaEl = document.getElementById("task-meta");
|
|
81
|
+
const linksEl = document.getElementById("task-links");
|
|
82
|
+
const evidenceEl = document.getElementById("task-evidence");
|
|
83
|
+
const runsEl = document.getElementById("task-runs");
|
|
84
|
+
if (!metaEl || !linksEl || !evidenceEl || !runsEl) return false;
|
|
85
|
+
if (titleEl) titleEl.textContent = taskId;
|
|
86
|
+
mountViewState(metaEl, "loading", "加载 Task…");
|
|
87
|
+
|
|
88
|
+
const [task, history, snapshot] = await Promise.all([
|
|
89
|
+
fetchJson(`/api/tasks/${encodeURIComponent(taskId)}`),
|
|
90
|
+
fetchJson(`/api/tasks/${encodeURIComponent(taskId)}/runs?limit=20`),
|
|
91
|
+
fetchJson("/api/snapshot"),
|
|
92
|
+
]);
|
|
93
|
+
if (snapshot) {
|
|
94
|
+
uiState.lastSnapshot = snapshot;
|
|
95
|
+
updateHeaderRefresh(snapshot.generatedAt);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
clearNode(metaEl);
|
|
99
|
+
clearNode(linksEl);
|
|
100
|
+
clearNode(evidenceEl);
|
|
101
|
+
clearNode(runsEl);
|
|
102
|
+
|
|
103
|
+
if (!task) {
|
|
104
|
+
mountViewState(metaEl, "error", `未找到 Task:${taskId}`);
|
|
105
|
+
mountRelationBar("task-relations", {}, snapshot);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
metaEl.appendChild(
|
|
110
|
+
metaGrid([
|
|
111
|
+
["Task ID", task.taskId],
|
|
112
|
+
["状态", badge(task.status)],
|
|
113
|
+
[
|
|
114
|
+
"Feature",
|
|
115
|
+
task.featureId
|
|
116
|
+
? objectRouteLink("feature", task.featureId, snapshot)
|
|
117
|
+
: el("span", "muted", "—"),
|
|
118
|
+
],
|
|
119
|
+
["更新时间", task.updatedAt ?? task.finishedAt ?? task.startedAt ?? "—"],
|
|
120
|
+
["最新 worker run", task.workerRunId ?? "—"],
|
|
121
|
+
["失败分类", task.failureCategory ?? "—"],
|
|
122
|
+
["建议跟进", task.recommendedFollowUp ?? "—"],
|
|
123
|
+
[
|
|
124
|
+
"活性",
|
|
125
|
+
task.liveness ? (LIVENESS_LABELS[task.liveness] ?? task.liveness) : "—",
|
|
126
|
+
],
|
|
127
|
+
]),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const batchRunId = findBatchRunIdForTask(snapshot, task);
|
|
131
|
+
const dag = findDagForRun(snapshot, task.workerRunId, task);
|
|
132
|
+
mountRelationBar(
|
|
133
|
+
"task-relations",
|
|
134
|
+
{
|
|
135
|
+
featureId: task.featureId,
|
|
136
|
+
taskId: task.taskId,
|
|
137
|
+
batchRunId: batchRunId ?? undefined,
|
|
138
|
+
workerRunId: task.workerRunId,
|
|
139
|
+
dagRunId: dag?.dagRunId ?? task.dagRunId,
|
|
140
|
+
},
|
|
141
|
+
snapshot,
|
|
142
|
+
);
|
|
143
|
+
linksEl.appendChild(el("h3", "section-heading", "真实关联"));
|
|
144
|
+
const linkList = el("div", "artifact-links");
|
|
145
|
+
linkList.appendChild(objectRouteLink("run", task.workerRunId, snapshot));
|
|
146
|
+
linkList.appendChild(document.createTextNode(" · "));
|
|
147
|
+
linkList.appendChild(
|
|
148
|
+
batchRunId
|
|
149
|
+
? objectRouteLink("batch", batchRunId, snapshot)
|
|
150
|
+
: el("span", "muted", "batch 缺失"),
|
|
151
|
+
);
|
|
152
|
+
linkList.appendChild(document.createTextNode(" · "));
|
|
153
|
+
linkList.appendChild(
|
|
154
|
+
dag
|
|
155
|
+
? objectRouteLink("dag", dag.dagRunId, snapshot)
|
|
156
|
+
: task.dagRunId
|
|
157
|
+
? el("span", "muted", task.dagRunId)
|
|
158
|
+
: el("span", "muted", "DAG 缺失"),
|
|
159
|
+
);
|
|
160
|
+
if (task.featureId) {
|
|
161
|
+
linkList.appendChild(document.createTextNode(" · "));
|
|
162
|
+
linkList.appendChild(objectRouteLink("feature", task.featureId, snapshot));
|
|
163
|
+
}
|
|
164
|
+
linksEl.appendChild(linkList);
|
|
165
|
+
|
|
166
|
+
evidenceEl.appendChild(el("h3", "section-heading", "失败证据"));
|
|
167
|
+
const r = task.artifactRefs ?? {};
|
|
168
|
+
const evidenceLinks = el("div", "artifact-links");
|
|
169
|
+
evidenceLinks.appendChild(artifactLink("报告", r.reportMarkdown));
|
|
170
|
+
evidenceLinks.appendChild(document.createTextNode(" "));
|
|
171
|
+
evidenceLinks.appendChild(artifactLink("诊断", r.doctorMarkdown));
|
|
172
|
+
evidenceLinks.appendChild(document.createTextNode(" "));
|
|
173
|
+
evidenceLinks.appendChild(artifactLink("收尾方案", r.closeoutDraft));
|
|
174
|
+
evidenceLinks.appendChild(document.createTextNode(" "));
|
|
175
|
+
evidenceLinks.appendChild(artifactLink("run 记录", r.runRecordPath));
|
|
176
|
+
if (
|
|
177
|
+
!r.reportMarkdown &&
|
|
178
|
+
!r.doctorMarkdown &&
|
|
179
|
+
!r.closeoutDraft &&
|
|
180
|
+
!r.runRecordPath
|
|
181
|
+
) {
|
|
182
|
+
evidenceEl.appendChild(el("p", "empty", "暂无 allowlist 内的证据路径。"));
|
|
183
|
+
} else {
|
|
184
|
+
evidenceEl.appendChild(evidenceLinks);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
runsEl.appendChild(el("h3", "section-heading", "运行历史"));
|
|
188
|
+
const runs = history?.runs ?? [];
|
|
189
|
+
if (runs.length === 0) {
|
|
190
|
+
mountViewState(runsEl, "empty", "暂无 ledger 运行记录。");
|
|
191
|
+
} else {
|
|
192
|
+
const rows = runs.map(taskRunHistoryRow);
|
|
193
|
+
runsEl.appendChild(
|
|
194
|
+
buildTable(
|
|
195
|
+
["worker run", "状态", "时间", "batch", "retryOf", "失败分类"],
|
|
196
|
+
rows,
|
|
197
|
+
),
|
|
198
|
+
);
|
|
199
|
+
if (history?.nextBefore) {
|
|
200
|
+
const more = el("button", "link-button", "加载更早的运行");
|
|
201
|
+
more.type = "button";
|
|
202
|
+
more.addEventListener("click", () => {
|
|
203
|
+
void loadOlderTaskRuns(taskId, history.nextBefore, runsEl);
|
|
204
|
+
});
|
|
205
|
+
runsEl.appendChild(more);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const active = ["running", "pending", "blocked", "stale"].includes(
|
|
210
|
+
(task.status ?? "").toLowerCase(),
|
|
211
|
+
);
|
|
212
|
+
return active;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function loadOlderTaskRuns(taskId, before, runsEl) {
|
|
216
|
+
const page = await fetchJson(
|
|
217
|
+
`/api/tasks/${encodeURIComponent(taskId)}/runs?limit=20&before=${encodeURIComponent(before)}`,
|
|
218
|
+
);
|
|
219
|
+
if (!page?.runs?.length) return;
|
|
220
|
+
const table = runsEl.querySelector("table");
|
|
221
|
+
if (!table) return;
|
|
222
|
+
const tbody = table.querySelector("tbody") || table;
|
|
223
|
+
for (const run of page.runs) {
|
|
224
|
+
const tr = document.createElement("tr");
|
|
225
|
+
for (const cell of taskRunHistoryRow(run).cells) {
|
|
226
|
+
const td = document.createElement("td");
|
|
227
|
+
if (cell instanceof Node) td.appendChild(cell);
|
|
228
|
+
else td.textContent = cell;
|
|
229
|
+
tr.appendChild(td);
|
|
230
|
+
}
|
|
231
|
+
tbody.appendChild(tr);
|
|
232
|
+
}
|
|
233
|
+
const btn = runsEl.querySelector("button.link-button");
|
|
234
|
+
if (btn) {
|
|
235
|
+
if (page.nextBefore) {
|
|
236
|
+
btn.onclick = () => {
|
|
237
|
+
void loadOlderTaskRuns(taskId, page.nextBefore, runsEl);
|
|
238
|
+
};
|
|
239
|
+
} else {
|
|
240
|
+
btn.remove();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function startTaskPolling(taskId) {
|
|
246
|
+
if (uiState.taskPollTimer) clearTimeout(uiState.taskPollTimer);
|
|
247
|
+
const generation = ++uiState.pollingGeneration;
|
|
248
|
+
const poll = async () => {
|
|
249
|
+
const active = await renderTaskDetail(taskId);
|
|
250
|
+
if (generation !== uiState.pollingGeneration || !active) return;
|
|
251
|
+
const delay = detailPollDelay("running", isPageVisible());
|
|
252
|
+
if (delay == null) return;
|
|
253
|
+
uiState.taskPollTimer = setTimeout(() => {
|
|
254
|
+
uiState.taskPollTimer = null;
|
|
255
|
+
void poll();
|
|
256
|
+
}, delay);
|
|
257
|
+
};
|
|
258
|
+
void poll();
|
|
259
|
+
}
|
|
260
|
+
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/** Observe UI view module (R5). */
|
|
2
|
+
import {
|
|
3
|
+
UI_TEXT,
|
|
4
|
+
LIVENESS_LABELS,
|
|
5
|
+
ARTIFACT_LABELS,
|
|
6
|
+
ARTIFACT_KEYS,
|
|
7
|
+
STEP_LABELS,
|
|
8
|
+
STEP_ORDER,
|
|
9
|
+
DAG_INSPECTOR_DEFAULT_WIDTH,
|
|
10
|
+
DAG_INSPECTOR_MIN_WIDTH,
|
|
11
|
+
DAG_INSPECTOR_MAX_WIDTH,
|
|
12
|
+
DAG_INSPECTOR_RIGHT_OFFSET,
|
|
13
|
+
DAG_INSPECTOR_VIEWPORT_MARGIN,
|
|
14
|
+
DAG_INSPECTOR_KEYBOARD_STEP,
|
|
15
|
+
TERMINAL_RUN_STATUSES,
|
|
16
|
+
DAG_EFFECTIVE_STATUS_LABELS,
|
|
17
|
+
STATUS_LABELS,
|
|
18
|
+
} from "../constants.js";
|
|
19
|
+
import { clearNode, el, metaGrid, buildTable } from "../dom.js";
|
|
20
|
+
import {
|
|
21
|
+
badge,
|
|
22
|
+
badgeClass,
|
|
23
|
+
statusLabel,
|
|
24
|
+
formatTs,
|
|
25
|
+
formatMs,
|
|
26
|
+
formatDuration,
|
|
27
|
+
formatAgo,
|
|
28
|
+
dagStatusBadge,
|
|
29
|
+
livenessBadge,
|
|
30
|
+
displayDurationMs,
|
|
31
|
+
derivedDurationMs,
|
|
32
|
+
summaryText,
|
|
33
|
+
truncateText,
|
|
34
|
+
repoBaseName,
|
|
35
|
+
parseMarkdownBlocks,
|
|
36
|
+
markdownLinkHref,
|
|
37
|
+
} from "../format.js";
|
|
38
|
+
import { parseRoute, navigate, buildHashPath } from "../router.js";
|
|
39
|
+
import {
|
|
40
|
+
fetchJson,
|
|
41
|
+
artifactUrl,
|
|
42
|
+
parseArtifactPreviewResponse,
|
|
43
|
+
} from "../api.js";
|
|
44
|
+
import {
|
|
45
|
+
objectRouteLink,
|
|
46
|
+
findFeatureInSnapshot,
|
|
47
|
+
featureLookupState,
|
|
48
|
+
buildObjectRelations,
|
|
49
|
+
renderObjectRelationBar,
|
|
50
|
+
} from "../relations.js";
|
|
51
|
+
import { copyText, renderRecommendedCommand } from "../copy.js";
|
|
52
|
+
import {
|
|
53
|
+
isPageVisible,
|
|
54
|
+
detailPollDelay,
|
|
55
|
+
dashboardPollDelay,
|
|
56
|
+
captureDagGraphViewportState,
|
|
57
|
+
updateDagGraphViewportState,
|
|
58
|
+
updateDagTimelineViewportState,
|
|
59
|
+
renderViewState,
|
|
60
|
+
mountViewState,
|
|
61
|
+
} from "../state.js";
|
|
62
|
+
import { uiState } from "../state.js";
|
|
63
|
+
import { sortPoolTasks } from "../format-pool.js";
|
|
64
|
+
import {
|
|
65
|
+
dagMetaVisibility,
|
|
66
|
+
isDagRunActive,
|
|
67
|
+
dashboardVisibleFeatures,
|
|
68
|
+
findDagForRun,
|
|
69
|
+
} from "../dag-model.js";
|
|
70
|
+
import { layoutDag } from "../dag-layout.js";
|
|
71
|
+
|
|
72
|
+
export async function mountBoundedEventTimeline(container, apiBase, title) {
|
|
73
|
+
clearNode(container);
|
|
74
|
+
container.appendChild(el("h3", "section-heading", title));
|
|
75
|
+
const note = el(
|
|
76
|
+
"p",
|
|
77
|
+
"process-timeline-note",
|
|
78
|
+
"有界历史(默认 50 / 最大 200);分页使用 cursor,与 SSE 行号 offset 分离。",
|
|
79
|
+
);
|
|
80
|
+
container.appendChild(note);
|
|
81
|
+
const scroll = el("div", "process-timeline-scroll event-history-scroll");
|
|
82
|
+
const listHost = el("div", "event-history-list");
|
|
83
|
+
scroll.appendChild(listHost);
|
|
84
|
+
container.appendChild(scroll);
|
|
85
|
+
const footer = el("div", "event-history-footer");
|
|
86
|
+
container.appendChild(footer);
|
|
87
|
+
|
|
88
|
+
let nextCursor = null;
|
|
89
|
+
let loading = false;
|
|
90
|
+
const accumulated = [];
|
|
91
|
+
|
|
92
|
+
const renderList = () => {
|
|
93
|
+
clearNode(listHost);
|
|
94
|
+
if (accumulated.length === 0) {
|
|
95
|
+
mountViewState(listHost, "empty", "暂无事件");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const ul = el("ul", "process-timeline");
|
|
99
|
+
for (let index = 0; index < accumulated.length; index += 1) {
|
|
100
|
+
const event = accumulated[index];
|
|
101
|
+
const li = el(
|
|
102
|
+
"li",
|
|
103
|
+
`process-timeline-item process-timeline-event has-time${index === 0 ? " is-first" : ""}${index === accumulated.length - 1 ? " is-last" : ""}`,
|
|
104
|
+
);
|
|
105
|
+
const rail = el("span", "process-timeline-rail");
|
|
106
|
+
rail.appendChild(el("span", "process-timeline-dot"));
|
|
107
|
+
li.appendChild(rail);
|
|
108
|
+
li.appendChild(
|
|
109
|
+
el("span", "process-timeline-time", formatTs(event.at) || "—"),
|
|
110
|
+
);
|
|
111
|
+
const label = `${event.type ?? "event"}${event.taskId ? ` · ${event.taskId}` : ""}${event.workerRunId ? ` · ${event.workerRunId}` : ""}`;
|
|
112
|
+
li.appendChild(el("span", "process-timeline-label", label));
|
|
113
|
+
ul.appendChild(li);
|
|
114
|
+
}
|
|
115
|
+
listHost.appendChild(ul);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const loadPage = async (cursor) => {
|
|
119
|
+
if (loading) return;
|
|
120
|
+
loading = true;
|
|
121
|
+
if (accumulated.length === 0) {
|
|
122
|
+
mountViewState(listHost, "loading", "加载事件…");
|
|
123
|
+
}
|
|
124
|
+
const qs = new URLSearchParams();
|
|
125
|
+
qs.set("limit", "50");
|
|
126
|
+
if (cursor) qs.set("cursor", cursor);
|
|
127
|
+
const page = await fetchJson(`${apiBase}?${qs.toString()}`);
|
|
128
|
+
loading = false;
|
|
129
|
+
if (!page) {
|
|
130
|
+
mountViewState(listHost, "error", "无法加载事件历史。");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const events = page.events ?? [];
|
|
134
|
+
for (const event of events) accumulated.push(event);
|
|
135
|
+
nextCursor = page.nextCursor ?? null;
|
|
136
|
+
renderList();
|
|
137
|
+
clearNode(footer);
|
|
138
|
+
if (page.corruptLineCount > 0) {
|
|
139
|
+
footer.appendChild(
|
|
140
|
+
renderViewState(
|
|
141
|
+
"degraded",
|
|
142
|
+
`跳过损坏行 ${page.corruptLineCount} 条(failure-safe)。`,
|
|
143
|
+
),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (nextCursor) {
|
|
147
|
+
const btn = el(
|
|
148
|
+
"button",
|
|
149
|
+
"btn-secondary event-history-more",
|
|
150
|
+
"加载更早事件",
|
|
151
|
+
);
|
|
152
|
+
btn.type = "button";
|
|
153
|
+
btn.addEventListener("click", () => {
|
|
154
|
+
void loadPage(nextCursor);
|
|
155
|
+
});
|
|
156
|
+
footer.appendChild(btn);
|
|
157
|
+
} else if (accumulated.length > 0) {
|
|
158
|
+
footer.appendChild(el("p", "muted", "已到历史边界"));
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
await loadPage(null);
|
|
163
|
+
}
|
package/dist/worker/preflight.js
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
import { access } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { controllerIdentityExpectationFailure, resolveControllerIdentity, } from "./loop-agent/loop-agent-client.js";
|
|
3
4
|
export const DEFAULT_CHECK_REPO_TIMEOUT_MS = 300_000;
|
|
4
5
|
export async function preflightTargetRepo(input) {
|
|
5
6
|
const commands = [];
|
|
7
|
+
const identity = resolveControllerIdentity(input.client);
|
|
8
|
+
const expectationFailure = controllerIdentityExpectationFailure(identity, input.expectation);
|
|
9
|
+
if (expectationFailure) {
|
|
10
|
+
return {
|
|
11
|
+
ok: false,
|
|
12
|
+
code: expectationFailure.code,
|
|
13
|
+
message: expectationFailure.message,
|
|
14
|
+
commands,
|
|
15
|
+
controllerIdentity: identity,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
6
18
|
if (!(await exists(path.join(input.repoRoot, "harness.json")))) {
|
|
7
19
|
return {
|
|
8
20
|
ok: false,
|
|
9
21
|
code: "target-not-initialized",
|
|
10
22
|
message: "target repo is missing harness.json",
|
|
11
23
|
commands,
|
|
24
|
+
controllerIdentity: identity,
|
|
12
25
|
};
|
|
13
26
|
}
|
|
14
27
|
if (!(await exists(path.join(input.repoRoot, ".harness")))) {
|
|
@@ -17,6 +30,7 @@ export async function preflightTargetRepo(input) {
|
|
|
17
30
|
code: "target-not-initialized",
|
|
18
31
|
message: "target repo is missing .harness",
|
|
19
32
|
commands,
|
|
33
|
+
controllerIdentity: identity,
|
|
20
34
|
};
|
|
21
35
|
}
|
|
22
36
|
const version = await input.client.run(["--version"], {
|
|
@@ -30,8 +44,36 @@ export async function preflightTargetRepo(input) {
|
|
|
30
44
|
code: "version-failed",
|
|
31
45
|
message: "loop-agent --version failed",
|
|
32
46
|
commands,
|
|
47
|
+
controllerIdentity: identity,
|
|
33
48
|
};
|
|
34
49
|
}
|
|
50
|
+
const reportedVersion = version.stdout.trim();
|
|
51
|
+
let controllerIdentity = identity
|
|
52
|
+
? { ...identity, reportedVersion }
|
|
53
|
+
: undefined;
|
|
54
|
+
if (identity && reportedVersion !== identity.packageVersion) {
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
code: "reported-version-mismatch",
|
|
58
|
+
message: `reported version ${reportedVersion} does not match package version ${identity.packageVersion}`,
|
|
59
|
+
commands,
|
|
60
|
+
controllerIdentity,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (identity && input.client.observeReportedVersion) {
|
|
64
|
+
try {
|
|
65
|
+
controllerIdentity = input.client.observeReportedVersion(reportedVersion) ?? controllerIdentity;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
code: "reported-version-mismatch",
|
|
71
|
+
message: error instanceof Error ? error.message : String(error),
|
|
72
|
+
commands,
|
|
73
|
+
controllerIdentity,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
35
77
|
const inspect = await input.client.run(["inspect"], {
|
|
36
78
|
cwd: input.repoRoot,
|
|
37
79
|
artifactName: "inspect",
|
|
@@ -44,6 +86,7 @@ export async function preflightTargetRepo(input) {
|
|
|
44
86
|
code: "inspect-failed",
|
|
45
87
|
message: "loop-agent inspect failed or did not return JSON",
|
|
46
88
|
commands,
|
|
89
|
+
controllerIdentity,
|
|
47
90
|
};
|
|
48
91
|
}
|
|
49
92
|
const docsAudit = await input.client.run(["docs", "audit"], {
|
|
@@ -57,6 +100,7 @@ export async function preflightTargetRepo(input) {
|
|
|
57
100
|
code: "docs-audit-failed",
|
|
58
101
|
message: "loop-agent docs audit failed",
|
|
59
102
|
commands,
|
|
103
|
+
controllerIdentity,
|
|
60
104
|
};
|
|
61
105
|
}
|
|
62
106
|
const gitStatus = await input.client.runExternal("git", ["status", "--short", "--branch"], {
|
|
@@ -70,6 +114,7 @@ export async function preflightTargetRepo(input) {
|
|
|
70
114
|
code: "git-status-failed",
|
|
71
115
|
message: "git status --short --branch failed",
|
|
72
116
|
commands,
|
|
117
|
+
controllerIdentity,
|
|
73
118
|
};
|
|
74
119
|
}
|
|
75
120
|
if (input.runCheckRepo) {
|
|
@@ -86,15 +131,18 @@ export async function preflightTargetRepo(input) {
|
|
|
86
131
|
code: "check-repo-failed",
|
|
87
132
|
message: `${[command, ...args].join(" ")} failed`,
|
|
88
133
|
commands,
|
|
134
|
+
controllerIdentity,
|
|
89
135
|
};
|
|
90
136
|
}
|
|
91
137
|
}
|
|
92
138
|
return {
|
|
93
139
|
ok: true,
|
|
94
|
-
version:
|
|
140
|
+
version: reportedVersion,
|
|
141
|
+
reportedVersion: controllerIdentity?.reportedVersion,
|
|
95
142
|
inspect: inspect.json,
|
|
96
143
|
gitStatus: gitStatus.stdout,
|
|
97
144
|
commands,
|
|
145
|
+
controllerIdentity,
|
|
98
146
|
};
|
|
99
147
|
}
|
|
100
148
|
async function exists(filePath) {
|
|
@@ -3,12 +3,18 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { formatDuration, noopProgressReporter } from "../progress-reporter.js";
|
|
5
5
|
import { redactForPreview, truncatePreview } from "../observability/events.js";
|
|
6
|
+
import { controllerIdentityExpectationFailure, resolveControllerIdentity, } from "../loop-agent/loop-agent-client.js";
|
|
6
7
|
import { materializeTaskSpec, } from "../materialize/harness-task-materializer.js";
|
|
7
8
|
import { preflightTargetRepo } from "../preflight.js";
|
|
8
9
|
import { getTaskPoolRoot } from "../pool/run-store.js";
|
|
9
10
|
export const DEFAULT_RUN_DAG_TIMEOUT_MS = 1_800_000;
|
|
10
11
|
export const MAX_WORKER_TIMEOUT_MS = 7_200_000;
|
|
11
12
|
export async function runTaskSpec(options) {
|
|
13
|
+
let controllerIdentity = resolveControllerIdentity(options.client, options.controllerIdentity);
|
|
14
|
+
const identityFailure = controllerIdentityExpectationFailure(controllerIdentity, options.controllerExpectation);
|
|
15
|
+
if (identityFailure) {
|
|
16
|
+
throw new Error(`${identityFailure.code}: ${identityFailure.message}`);
|
|
17
|
+
}
|
|
12
18
|
const now = options.now ?? new Date();
|
|
13
19
|
const workerRunId = options.workerRunId ?? buildWorkerRunId(options.taskSpec.id, now);
|
|
14
20
|
const commands = [];
|
|
@@ -26,11 +32,13 @@ export async function runTaskSpec(options) {
|
|
|
26
32
|
repoRoot: options.repoRoot,
|
|
27
33
|
client,
|
|
28
34
|
...(typeof options.preflight === "object" ? options.preflight : {}),
|
|
35
|
+
...(options.controllerExpectation ? { expectation: options.controllerExpectation } : {}),
|
|
29
36
|
});
|
|
30
37
|
if (!preflight.ok) {
|
|
31
38
|
progress.note(`preflight FAILED: ${preflight.code}`);
|
|
32
39
|
throw new Error(`target repo preflight failed: ${preflight.code}: ${preflight.message}`);
|
|
33
40
|
}
|
|
41
|
+
controllerIdentity = preflight.controllerIdentity ?? controllerIdentity;
|
|
34
42
|
});
|
|
35
43
|
progress.step(`preflight ok in ${formatDuration(Date.now() - preflightStartedAt)}`);
|
|
36
44
|
}
|
|
@@ -66,7 +74,6 @@ export async function runTaskSpec(options) {
|
|
|
66
74
|
dagPath,
|
|
67
75
|
"--cwd",
|
|
68
76
|
options.repoRoot,
|
|
69
|
-
...noCursorArgs(options.taskSpec),
|
|
70
77
|
], true, undefined, eventCtx);
|
|
71
78
|
});
|
|
72
79
|
progress.step(`dag generated in ${formatDuration(Date.now() - dagGenStartedAt)}`);
|
|
@@ -90,7 +97,6 @@ export async function runTaskSpec(options) {
|
|
|
90
97
|
"--strict-governance",
|
|
91
98
|
"--spine-task",
|
|
92
99
|
materializeManifest.harnessTaskId,
|
|
93
|
-
...forbidCursorArgs(options.taskSpec),
|
|
94
100
|
], true, undefined, eventCtx);
|
|
95
101
|
});
|
|
96
102
|
progress.step(`dag validated in ${formatDuration(Date.now() - validateStartedAt)}`);
|
|
@@ -111,7 +117,6 @@ export async function runTaskSpec(options) {
|
|
|
111
117
|
"--events-jsonl",
|
|
112
118
|
dagEventsPath,
|
|
113
119
|
...maxConcurrentArgs(options.taskSpec),
|
|
114
|
-
...noCursorArgs(options.taskSpec),
|
|
115
120
|
], resolveRunDagTimeoutMs(options.taskSpec), eventCtx);
|
|
116
121
|
});
|
|
117
122
|
progress.step(`run-dag finished in ${formatDuration(Date.now() - runDagStartedAt)}`);
|
|
@@ -180,6 +185,7 @@ export async function runTaskSpec(options) {
|
|
|
180
185
|
reportDecision,
|
|
181
186
|
commands,
|
|
182
187
|
...(failureArtifacts ? { failureArtifacts } : {}),
|
|
188
|
+
...(controllerIdentity ? { controllerIdentity } : {}),
|
|
183
189
|
};
|
|
184
190
|
await writeFile(runRecordPath, `${JSON.stringify(record, null, 2)}\n`, "utf-8");
|
|
185
191
|
emit(progress, {
|
|
@@ -407,6 +413,12 @@ class RecordingRunTaskClient {
|
|
|
407
413
|
this.delegate = delegate;
|
|
408
414
|
this.records = records;
|
|
409
415
|
}
|
|
416
|
+
getIdentity() {
|
|
417
|
+
return this.delegate.getIdentity?.();
|
|
418
|
+
}
|
|
419
|
+
observeReportedVersion(reportedVersion) {
|
|
420
|
+
return this.delegate.observeReportedVersion?.(reportedVersion);
|
|
421
|
+
}
|
|
410
422
|
async run(args, options) {
|
|
411
423
|
const result = await this.delegate.run(args, options);
|
|
412
424
|
this.records.push({
|
|
@@ -452,6 +464,12 @@ class ObservedRunTaskClient {
|
|
|
452
464
|
this.delegate = delegate;
|
|
453
465
|
this.eventCtx = eventCtx;
|
|
454
466
|
}
|
|
467
|
+
getIdentity() {
|
|
468
|
+
return this.delegate.getIdentity();
|
|
469
|
+
}
|
|
470
|
+
observeReportedVersion(reportedVersion) {
|
|
471
|
+
return this.delegate.observeReportedVersion(reportedVersion);
|
|
472
|
+
}
|
|
455
473
|
run(args, options) {
|
|
456
474
|
return this.runObserved(args, options, (observedOptions) => this.delegate.run(args, observedOptions));
|
|
457
475
|
}
|
|
@@ -544,12 +562,6 @@ function commandRecordName(args, artifactName) {
|
|
|
544
562
|
return "new-task";
|
|
545
563
|
return artifactName;
|
|
546
564
|
}
|
|
547
|
-
function noCursorArgs(taskSpec) {
|
|
548
|
-
return taskSpec.loop_agent.no_cursor ? ["--no-cursor"] : [];
|
|
549
|
-
}
|
|
550
|
-
function forbidCursorArgs(taskSpec) {
|
|
551
|
-
return taskSpec.loop_agent.no_cursor ? ["--forbid-executor", "cursor"] : [];
|
|
552
|
-
}
|
|
553
565
|
function maxConcurrentArgs(taskSpec) {
|
|
554
566
|
return ["--max-concurrent", String(taskSpec.loop_agent.max_concurrent)];
|
|
555
567
|
}
|
|
@@ -563,14 +575,12 @@ function strictModelsArgs(piModel) {
|
|
|
563
575
|
}
|
|
564
576
|
/**
|
|
565
577
|
* Rewrite `executorModels.pi.{LOW,MED,HIGH}` in an already-generated DAG JSON
|
|
566
|
-
* so every pi executor node resolves to the smoke override model.
|
|
567
|
-
* slots are left untouched.
|
|
578
|
+
* so every pi executor node resolves to the smoke override model.
|
|
568
579
|
*/
|
|
569
580
|
export async function applyPiModelOverride(dagPath, piModel) {
|
|
570
581
|
const raw = await readFile(dagPath, "utf-8");
|
|
571
582
|
const spec = JSON.parse(raw);
|
|
572
583
|
spec.executorModels = {
|
|
573
|
-
...(spec.executorModels?.cursor ? { cursor: spec.executorModels.cursor } : {}),
|
|
574
584
|
pi: {
|
|
575
585
|
LOW: piModel,
|
|
576
586
|
MED: piModel,
|