@tea-agent/loop-agent 0.8.0 → 0.10.0-alpha.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 -0
- package/CHANGELOG.md +101 -1
- package/README.md +69 -5
- package/dist/application/dag/args.js +13 -16
- package/dist/application/dag/generate-task-dag.js +32 -2
- package/dist/application/dag/run-dag.js +1 -27
- package/dist/application/dag/validate-dag.js +2 -2
- package/dist/application/loop/run-action.js +0 -4
- package/dist/cli/command-definitions.js +7 -11
- package/dist/cli/program.js +9 -21
- package/dist/commands/cursor-prompt.js +42 -82
- package/dist/commands/dag-approve.js +36 -0
- package/dist/commands/dag-reconcile-run.js +118 -0
- package/dist/commands/delegate.js +75 -77
- package/dist/commands/doctor.js +0 -18
- package/dist/commands/init.js +60 -40
- package/dist/commands/instructions.js +7 -10
- package/dist/commands/loop.js +4 -20
- 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 +75 -9
- package/dist/governance/checks.js +6 -3
- package/dist/governance/manifest-types.js +33 -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 +353 -0
- package/dist/shared/reference-context.js +48 -22
- 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 +30 -13
- package/dist/task/delegate.js +9 -21
- package/dist/task/runtime.js +2 -3
- package/dist/worker/cli.js +243 -0
- package/dist/worker/closeout/apply.js +73 -0
- package/dist/worker/closeout/preview.js +30 -0
- package/dist/worker/delivery/final-verification.js +194 -0
- package/dist/worker/delivery/git-transaction.js +354 -0
- package/dist/worker/delivery/package.js +502 -0
- package/dist/worker/feature/decision-loader.js +68 -0
- package/dist/worker/feature/discover.js +14 -0
- package/dist/worker/feature/next-action.js +74 -0
- package/dist/worker/feature/reducer.js +133 -0
- package/dist/worker/feature/review.js +502 -0
- package/dist/worker/feature/run.js +365 -0
- package/dist/worker/feature/types.js +1 -0
- package/dist/worker/follow-up/approve.js +270 -0
- package/dist/worker/follow-up/factory.js +234 -0
- package/dist/worker/follow-up/paths.js +25 -0
- package/dist/worker/follow-up/policy.js +26 -0
- package/dist/worker/follow-up/schema.js +93 -0
- package/dist/worker/follow-up/store.js +96 -0
- package/dist/worker/loop-agent/loop-agent-client.js +345 -101
- package/dist/worker/metrics/projector.js +139 -0
- package/dist/worker/observability/read-model.js +282 -15
- package/dist/worker/observe/paths.js +17 -5
- package/dist/worker/observe/routes.js +78 -20
- package/dist/worker/observe/server.js +8 -6
- package/dist/worker/observe/static/app.js +1045 -177
- package/dist/worker/observe/static/index.html +70 -43
- package/dist/worker/observe/static/styles.css +553 -610
- package/dist/worker/pool/run-store.js +14 -2
- package/dist/worker/pool/validation.js +59 -0
- package/dist/worker/preflight.js +49 -1
- package/dist/worker/report/morning-report.js +41 -6
- package/dist/worker/run-task/run-task.js +23 -13
- package/dist/worker/runner/run-ready.js +89 -11
- package/dist/worker/task-spec/schema.js +0 -1
- package/dist/workflows/dag/convergence/controller.js +1 -1
- package/dist/workflows/dag/executor-registry.js +0 -2
- package/dist/workflows/dag/governance-profile.js +10 -0
- package/dist/workflows/dag/init-hybrid.js +601 -26
- package/dist/workflows/dag/lifecycle.js +146 -0
- package/dist/workflows/dag/node-execution.js +64 -7
- package/dist/workflows/dag/prompt.js +16 -0
- package/dist/workflows/dag/report.js +2 -0
- package/dist/workflows/dag/runner.js +176 -119
- package/dist/workflows/dag/scheduler.js +7 -2
- package/dist/workflows/dag/skill-snapshot.js +527 -0
- package/dist/workflows/dag/types.js +45 -9
- package/dist/workflows/dag/validate.js +5 -8
- 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 +35 -12
- package/docs/agent-dag-recovery-playbook.md +1 -1
- package/docs/architecture/README.md +26 -0
- package/docs/architecture/dag-execution.md +134 -0
- package/docs/architecture/evolution.md +52 -0
- package/docs/architecture/facts-and-state.md +58 -0
- package/docs/architecture/runtime-boundaries.md +41 -15
- 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 +39 -13
- package/docs/development-principles.md +1 -1
- package/docs/exec-plans/active/README.md +2 -2
- package/docs/exec-plans/completed/README.md +21 -0
- package/docs/feature-workflow.md +44 -4
- package/docs/init-surface.manifest.json +63 -1
- package/docs/loop-agent-harness.md +65 -3
- package/docs/progress/README.md +27 -0
- package/docs/reports/README.md +74 -5
- package/docs/skills/README.md +2 -1
- package/docs/skills/vetted-skill-registry.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +4 -2
- 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 +1 -2
- package/docs/templates/agent-dag.supervised-implementation.json +1 -6
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +131 -0
- package/docs/templates/backend-test-dag.json +213 -0
- package/docs/templates/backend-test-dag.retrospect.prompt.md +128 -0
- package/docs/templates/backend-test-dag.review-cases.prompt.md +85 -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 +8 -5
- package/docs/templates/hybrid-dag.json +1 -6
- package/docs/templates/init-evolution-review.md +4 -2
- package/docs/templates/interactive-ui-round2-experiment.md +1 -1
- package/docs/templates/product-line/task.yaml +0 -1
- package/docs/templates/worker-dogfood-evidence.md +28 -0
- package/docs/templates/worker-dogfood-setup.md +20 -0
- package/docs/verification-matrix.md +17 -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 +6 -11
- package/package.json +22 -44
- package/scripts/check-product-line-docs.sh +10 -3
- package/scripts/check-task-pool-root.sh +1 -1
- 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 +48 -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/init-capability-evolution/SKILL.md +1 -0
- package/skills/loop-agent/SKILL.md +11 -9
- package/skills/loop-agent/references/command-reference.md +28 -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 +13 -15
- 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/verification-and-failure-handling.md +8 -3
- 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
package/dist/commands/init.js
CHANGED
|
@@ -367,41 +367,54 @@ set -euo pipefail
|
|
|
367
367
|
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
368
368
|
cd "\${ROOT_DIR}"
|
|
369
369
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
missing=()
|
|
384
|
-
for ref in "\${required_refs[@]}"; do
|
|
385
|
-
[[ -f "skills/loop-agent/\${ref}" ]] || missing+=("\${ref}")
|
|
386
|
-
done
|
|
370
|
+
check_skill() {
|
|
371
|
+
local skill_name="$1"
|
|
372
|
+
shift
|
|
373
|
+
local skill_dir="skills/\${skill_name}"
|
|
374
|
+
local skill_md="\${skill_dir}/SKILL.md"
|
|
375
|
+
local missing=()
|
|
376
|
+
|
|
377
|
+
if [[ ! -f "\${skill_md}" ]]; then
|
|
378
|
+
echo "skill entry 检查失败:缺少 \${skill_md}" >&2
|
|
379
|
+
exit 1
|
|
380
|
+
fi
|
|
381
|
+
grep -Eq "^name:[[:space:]]*\${skill_name}[[:space:]]*$" "\${skill_md}" || missing+=("frontmatter name")
|
|
382
|
+
grep -Eq '^description:[[:space:]]*[^[:space:]]' "\${skill_md}" || missing+=("frontmatter description")
|
|
387
383
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
384
|
+
local ref
|
|
385
|
+
for ref in "$@"; do
|
|
386
|
+
[[ -f "\${skill_dir}/\${ref}" ]] || missing+=("\${ref}")
|
|
387
|
+
grep -Fq "\${ref}" "\${skill_md}" || missing+=("SKILL.md -> \${ref}")
|
|
388
|
+
done
|
|
389
|
+
if (( \${#missing[@]} > 0 )); then
|
|
390
|
+
echo "skill entry 检查失败(\${skill_name}):" >&2
|
|
391
|
+
printf ' - %s\\n' "\${missing[@]}" >&2
|
|
392
|
+
exit 1
|
|
393
|
+
fi
|
|
392
394
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
395
|
+
local line_count
|
|
396
|
+
line_count="$(wc -l < "\${skill_md}" | tr -d ' ')"
|
|
397
|
+
if [[ "\${line_count}" =~ ^[0-9]+$ && "\${line_count}" -gt 180 ]]; then
|
|
398
|
+
echo "skill entry 检查失败:\${skill_md} 行数为 \${line_count},入口应保持精简" >&2
|
|
399
|
+
exit 1
|
|
400
|
+
fi
|
|
401
|
+
echo "skill entry 检查通过:\${skill_name} references ok, lines=\${line_count}"
|
|
402
|
+
}
|
|
398
403
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
404
|
+
check_skill "loop-agent" \\
|
|
405
|
+
"references/harness-policy.md" \\
|
|
406
|
+
"references/hybrid-dag.md" \\
|
|
407
|
+
"references/verification-and-failure-handling.md" \\
|
|
408
|
+
"references/command-reference.md"
|
|
409
|
+
check_skill "agent-worker" "references/agent-worker-operator.md"
|
|
403
410
|
|
|
404
|
-
|
|
411
|
+
description_line="$(grep -E '^description:' skills/agent-worker/SKILL.md | head -n 1 | tr '[:upper:]' '[:lower:]')"
|
|
412
|
+
for term in "agent-worker" "feature packet" "taskspec" "task pool" "self-host" "candidate" "loop-agent"; do
|
|
413
|
+
if [[ "\${description_line}" != *"\${term}"* ]]; then
|
|
414
|
+
echo "skill entry 检查失败(agent-worker):description 缺少触发/路由词 \${term}" >&2
|
|
415
|
+
exit 1
|
|
416
|
+
fi
|
|
417
|
+
done
|
|
405
418
|
`;
|
|
406
419
|
const INIT_CHECK_REPO_SH = `#!/usr/bin/env bash
|
|
407
420
|
set -euo pipefail
|
|
@@ -443,11 +456,20 @@ fi
|
|
|
443
456
|
feature_dirs=()
|
|
444
457
|
for root in features product/features dogfood/features; do
|
|
445
458
|
[[ -d "\${root}" ]] || continue
|
|
446
|
-
while IFS= read -r
|
|
447
|
-
|
|
448
|
-
|
|
459
|
+
while IFS= read -r feature_dir; do
|
|
460
|
+
if [[ ! -f "\${feature_dir}/acceptance.yaml" ]]; then
|
|
461
|
+
echo "product-line docs check failed: incomplete feature packet missing acceptance.yaml: \${feature_dir}" >&2
|
|
462
|
+
exit 1
|
|
463
|
+
fi
|
|
464
|
+
feature_dirs+=("\${feature_dir}")
|
|
465
|
+
done < <(find "\${root}" -mindepth 1 -maxdepth 1 -type d | sort)
|
|
449
466
|
done
|
|
450
467
|
|
|
468
|
+
if [[ \${#feature_dirs[@]} -eq 0 ]]; then
|
|
469
|
+
echo "product-line docs check skipped: no feature packets found"
|
|
470
|
+
exit 0
|
|
471
|
+
fi
|
|
472
|
+
|
|
451
473
|
for feature_dir in "\${feature_dirs[@]}"; do
|
|
452
474
|
echo "==> validate product-line feature: \${feature_dir}"
|
|
453
475
|
agent-worker task validate-feature "\${feature_dir}"
|
|
@@ -811,11 +833,9 @@ function buildHarness(input) {
|
|
|
811
833
|
const existingExecutors = isRecord(input.existing.executors)
|
|
812
834
|
? input.existing.executors
|
|
813
835
|
: {};
|
|
814
|
-
// Cursor executor
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
delete templateExecutors.cursor;
|
|
818
|
-
}
|
|
836
|
+
// Cursor executor must never be projected; Pi-only governed runtime.
|
|
837
|
+
delete templateExecutors.cursor;
|
|
838
|
+
delete existingExecutors.cursor;
|
|
819
839
|
const mergedExecutors = mergeRecord(templateExecutors, existingExecutors);
|
|
820
840
|
// requiresApiKey is Cursor-facing only; never project it on pi.
|
|
821
841
|
if (isRecord(mergedExecutors.pi) && "requiresApiKey" in mergedExecutors.pi) {
|
|
@@ -1937,7 +1957,7 @@ export async function checkInitUpdate(input) {
|
|
|
1937
1957
|
deterministicActions.push({
|
|
1938
1958
|
type: "strip-pi-requires-api-key",
|
|
1939
1959
|
path: "harness.json",
|
|
1940
|
-
reason: "executors.pi.requiresApiKey is unused; strip it while
|
|
1960
|
+
reason: "executors.pi.requiresApiKey is unused; strip it while preserving Pi model configuration",
|
|
1941
1961
|
});
|
|
1942
1962
|
}
|
|
1943
1963
|
}
|
|
@@ -153,23 +153,20 @@ export async function buildInstructions(repoRoot, stage, taskId) {
|
|
|
153
153
|
],
|
|
154
154
|
}));
|
|
155
155
|
}
|
|
156
|
-
const artifactSteps = [
|
|
157
|
-
"analyze",
|
|
158
|
-
"plan",
|
|
159
|
-
"implement",
|
|
160
|
-
"verify",
|
|
161
|
-
"retrospective",
|
|
162
|
-
];
|
|
163
156
|
return refreshInstructionBlockers(withMissingDependencies({
|
|
164
157
|
...base,
|
|
165
158
|
outputPath: path.join(taskDir, "artifacts"),
|
|
166
159
|
dependencies: [paths.taskConfigPath, paths.statePath],
|
|
167
160
|
template: {
|
|
168
|
-
requiredHeadings:
|
|
161
|
+
requiredHeadings: [
|
|
162
|
+
getArtifactRelativePath("implement"),
|
|
163
|
+
getArtifactRelativePath("verify"),
|
|
164
|
+
],
|
|
169
165
|
},
|
|
170
166
|
completionCriteria: [
|
|
171
|
-
"
|
|
172
|
-
"
|
|
167
|
+
"修改记录.md and 验证结果.md are promote-run bridge artifacts filled from completed DAG/one-shot evidence",
|
|
168
|
+
"do not hand-write L1 分析报告/实现计划/复盘报告; those are not seeded or required",
|
|
169
|
+
"verification artifact summarizes available completed-run evidence; inspect the run facts for exact shell commands and full output",
|
|
173
170
|
],
|
|
174
171
|
}));
|
|
175
172
|
}
|
package/dist/commands/loop.js
CHANGED
|
@@ -7,10 +7,9 @@ function usage() {
|
|
|
7
7
|
" loop status <task-id> [--json]",
|
|
8
8
|
" loop run <task-id> --action shell-verify [--command <cmd>]... [--timeout-ms N] [--cwd <repo-relative-dir>]",
|
|
9
9
|
" loop run <task-id> --action pi-review [--provider <id>] [--model <id>] [--timeout-ms N] [--cwd <repo-relative-dir>]",
|
|
10
|
-
" loop run <task-id> --action
|
|
11
|
-
" loop run <task-id> --action dag [--execute] [--dag <path>] [--run-id <id>] [--max-concurrent N] [--cwd <repo-relative-dir>] [--no-cursor]",
|
|
10
|
+
" loop run <task-id> --action dag [--execute] [--dag <path>] [--run-id <id>] [--max-concurrent N] [--cwd <repo-relative-dir>]",
|
|
12
11
|
" loop run <task-id> --action workflow [--profile pr-review|--workflow <path>] [--execute] [--run-id <id>] [--changed-file <path>]... [--cwd <repo-relative-dir>]",
|
|
13
|
-
" loop run <task-id> --auto [--max-rounds N] [--
|
|
12
|
+
" loop run <task-id> --auto [--max-rounds N] [--approval-granted]",
|
|
14
13
|
" loop record-round <task-id> --action <name> --result <summary> --lesson <summary> --next <summary> --decision <continue|pause|blocked|complete> [--verification <summary>] [--failure-category <category>] [--ref <path>]...",
|
|
15
14
|
" loop add-signal <task-id> --type <human_followup|approval|scope_changed|review_feedback> --message <text> [--urgent] [--ref <path>]...",
|
|
16
15
|
" loop closeout <task-id>",
|
|
@@ -144,7 +143,7 @@ export async function runLoop(repoRoot, args) {
|
|
|
144
143
|
}
|
|
145
144
|
const record = await runLoopAuto(repoRoot, taskId, {
|
|
146
145
|
maxRounds,
|
|
147
|
-
|
|
146
|
+
approvalGranted: rest.includes("--approval-granted"),
|
|
148
147
|
});
|
|
149
148
|
console.log(JSON.stringify(record, null, 2));
|
|
150
149
|
return;
|
|
@@ -174,20 +173,6 @@ export async function runLoop(repoRoot, args) {
|
|
|
174
173
|
console.log(JSON.stringify(record, null, 2));
|
|
175
174
|
return;
|
|
176
175
|
}
|
|
177
|
-
if (action === "cursor-fix") {
|
|
178
|
-
const record = await runLoopAction({
|
|
179
|
-
action,
|
|
180
|
-
repoRoot,
|
|
181
|
-
taskId,
|
|
182
|
-
options: {
|
|
183
|
-
timeoutMs,
|
|
184
|
-
cwd: readFlag(rest, "--cwd"),
|
|
185
|
-
model: readFlag(rest, "--model"),
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
console.log(JSON.stringify(record, null, 2));
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
176
|
if (action === "dag") {
|
|
192
177
|
const maxConcurrentRaw = readFlag(rest, "--max-concurrent");
|
|
193
178
|
let maxConcurrent;
|
|
@@ -208,7 +193,6 @@ export async function runLoop(repoRoot, args) {
|
|
|
208
193
|
dagPath: readFlag(rest, "--dag"),
|
|
209
194
|
runId: readFlag(rest, "--run-id"),
|
|
210
195
|
maxConcurrent,
|
|
211
|
-
noCursor: rest.includes("--no-cursor"),
|
|
212
196
|
},
|
|
213
197
|
});
|
|
214
198
|
console.log(JSON.stringify(record, null, 2));
|
|
@@ -248,7 +232,7 @@ export async function runLoop(repoRoot, args) {
|
|
|
248
232
|
return;
|
|
249
233
|
}
|
|
250
234
|
if (action !== "shell-verify") {
|
|
251
|
-
throw new Error("loop run currently supports --action shell-verify|pi-review|
|
|
235
|
+
throw new Error("loop run currently supports --action shell-verify|pi-review|dag|workflow");
|
|
252
236
|
}
|
|
253
237
|
const record = await runLoopAction({
|
|
254
238
|
action: "shell-verify",
|
|
@@ -1,56 +1,5 @@
|
|
|
1
|
-
import { DEFAULT_CURSOR_MODEL } from './cursor-executor.js';
|
|
2
|
-
import { resolveExecutorModelMatrix } from './model-routing.js';
|
|
3
1
|
export const TASK_COMPLEXITY_TO_DAG = {
|
|
4
2
|
small: 'LOW',
|
|
5
3
|
medium: 'MED',
|
|
6
4
|
large: 'HIGH',
|
|
7
5
|
};
|
|
8
|
-
export function resolveCursorModel(overrides = {}, cursorConfig) {
|
|
9
|
-
return overrides.taskModel ?? cursorConfig?.defaultModel ?? DEFAULT_CURSOR_MODEL;
|
|
10
|
-
}
|
|
11
|
-
export function resolveCursorModelForTaskConfig(taskConfig, cursorConfig) {
|
|
12
|
-
if (taskConfig.cursorModel?.trim()) {
|
|
13
|
-
return taskConfig.cursorModel.trim();
|
|
14
|
-
}
|
|
15
|
-
const dagLevel = TASK_COMPLEXITY_TO_DAG[taskConfig.complexity ?? 'medium'];
|
|
16
|
-
const matrix = resolveExecutorModelMatrix('cursor', cursorConfig);
|
|
17
|
-
return matrix[dagLevel] ?? resolveCursorModel({}, cursorConfig);
|
|
18
|
-
}
|
|
19
|
-
export function resolveTaskExecutor(taskConfig, override) {
|
|
20
|
-
if (override)
|
|
21
|
-
return override;
|
|
22
|
-
return taskConfig.executor ?? 'pi';
|
|
23
|
-
}
|
|
24
|
-
export function resolveCursorExecutorConfig(manifest) {
|
|
25
|
-
const config = manifest.executors?.cursor;
|
|
26
|
-
if (config?.enabled === false)
|
|
27
|
-
return undefined;
|
|
28
|
-
return config;
|
|
29
|
-
}
|
|
30
|
-
export function assertCursorExecutorAvailable(manifest) {
|
|
31
|
-
const config = resolveCursorExecutorConfig(manifest);
|
|
32
|
-
if (!config) {
|
|
33
|
-
throw new Error('cursor executor is not registered in harness.json executors.cursor');
|
|
34
|
-
}
|
|
35
|
-
return config;
|
|
36
|
-
}
|
|
37
|
-
export function validateCursorTaskPreflight(taskConfig) {
|
|
38
|
-
if (taskConfig.executor !== 'cursor')
|
|
39
|
-
return;
|
|
40
|
-
const allowed = taskConfig.allowedPaths ?? [];
|
|
41
|
-
if (allowed.length === 0) {
|
|
42
|
-
// Read-only analysis tasks may omit allowedPaths; write tasks must declare scope explicitly.
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
export function assertCursorWriteTaskScope(taskConfig, changedPaths) {
|
|
47
|
-
if (taskConfig.executor !== 'cursor')
|
|
48
|
-
return;
|
|
49
|
-
if ((taskConfig.allowedPaths ?? []).length > 0)
|
|
50
|
-
return;
|
|
51
|
-
const codeChanges = changedPaths.filter((filePath) => !filePath.startsWith('.harness/tasks/'));
|
|
52
|
-
if (codeChanges.length > 0) {
|
|
53
|
-
throw new Error('cursor write task refused: allowedPaths is empty but git diff contains code changes. ' +
|
|
54
|
-
'Declare allowedPaths explicitly or keep the task read-only.');
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { writeTextArtifactFile } from "../infrastructure/harness/artifact-store.js";
|
|
3
3
|
import { executePiStep, } from "./pi-executor.js";
|
|
4
|
-
import { redactPromptForLog, truncateOutput } from "
|
|
4
|
+
import { redactPromptForLog, truncateOutput, } from "../shared/output-truncation.js";
|
|
5
5
|
import { readGitStatusPorcelain, runPostRunWriteGuard, } from "./shell-write-guard.js";
|
|
6
6
|
export const DAG_PI_READONLY_TOOLS = ["read", "grep", "find", "ls"];
|
|
7
7
|
export const DAG_PI_WRITE_TOOLS = [
|
package/dist/executors/dag.js
CHANGED
package/dist/executors/index.js
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import { DEFAULT_DAG_EXECUTOR_MODELS, } from '../workflows/dag/types.js';
|
|
2
|
-
export const DEFAULT_DAG_CURSOR_MODEL = "composer-2.5";
|
|
3
2
|
export const DEFAULT_DAG_MODELS = {
|
|
4
3
|
HIGH: "gpt-5.5",
|
|
5
|
-
MED: "
|
|
6
|
-
LOW: "
|
|
4
|
+
MED: "glm-5.2",
|
|
5
|
+
LOW: "gpt-5.3-codex-spark",
|
|
7
6
|
};
|
|
8
7
|
/**
|
|
9
8
|
* DAG executor model tier keys that may carry a per-complexity override.
|
|
10
9
|
*/
|
|
11
10
|
const EXECUTOR_MODEL_TIERS = ["LOW", "MED", "HIGH"];
|
|
12
11
|
/**
|
|
13
|
-
* Resolve the DAG executor model matrix for
|
|
14
|
-
* `executors.<name>` config.
|
|
12
|
+
* Resolve the DAG executor model matrix for Pi from harness `executors.pi`.
|
|
15
13
|
*
|
|
16
14
|
* Priority per tier (LOW/MED/HIGH):
|
|
17
15
|
* 1. execConfig[tier] (truthy and !== "default" sentinel)
|
|
18
16
|
* 2. execConfig.defaultModel (truthy and !== "default" sentinel)
|
|
19
|
-
* 3. DEFAULT_DAG_EXECUTOR_MODELS[
|
|
17
|
+
* 3. DEFAULT_DAG_EXECUTOR_MODELS.pi[tier]
|
|
20
18
|
*
|
|
21
19
|
* The "default" literal (injected by the schema `.default("default")`) and
|
|
22
20
|
* absent/undefined both mean "no override, fall through".
|
|
@@ -38,15 +36,17 @@ export function resolveExecutorModelMatrix(executor, execConfig) {
|
|
|
38
36
|
};
|
|
39
37
|
}
|
|
40
38
|
/**
|
|
41
|
-
* Resolve
|
|
42
|
-
* Single entry point for DAG generation and --strict-models baseline resolution.
|
|
39
|
+
* Resolve the Pi DAG executor model matrix from a harness manifest.
|
|
43
40
|
*/
|
|
44
41
|
export function resolveExecutorModelMatrices(manifest) {
|
|
45
42
|
return {
|
|
46
43
|
pi: resolveExecutorModelMatrix("pi", manifest.executors?.pi),
|
|
47
|
-
cursor: resolveExecutorModelMatrix("cursor", manifest.executors?.cursor),
|
|
48
44
|
};
|
|
49
45
|
}
|
|
46
|
+
/** @deprecated Prefer resolveExecutorModelMatrices; alias kept for call-site clarity. */
|
|
47
|
+
export function resolvePiModelMatrix(manifest) {
|
|
48
|
+
return resolveExecutorModelMatrices(manifest).pi;
|
|
49
|
+
}
|
|
50
50
|
export function resolveModelSelection(manifest, taskConfig, step, options) {
|
|
51
51
|
const retryAttempt = options?.retryAttempt ?? 0;
|
|
52
52
|
if (Object.keys(manifest.modelProfiles ?? {}).length === 0) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { writeDagNodeTextArtifact } from "../infrastructure/harness/artifact-store.js";
|
|
5
|
-
import { truncateOutput } from "
|
|
5
|
+
import { truncateOutput } from "../shared/output-truncation.js";
|
|
6
6
|
import { expandShellPreset, buildVerdictGateShellCommand } from "./shell-presets.js";
|
|
7
7
|
import { pathsChangedDuringRun, snapshotGitStatusPorcelain, validateShellWriteGuard, } from "./shell-write-guard.js";
|
|
8
8
|
import { buildShellProcessEnv } from "./shell-verification.js";
|
|
@@ -11,6 +11,7 @@ const SUMMARY_STDOUT_MAX = 4_000;
|
|
|
11
11
|
const SUMMARY_STDERR_MAX = 2_000;
|
|
12
12
|
const NODE_STDOUT_MAX = 12_000;
|
|
13
13
|
const NODE_STDERR_MAX = 8_000;
|
|
14
|
+
const MAX_BUFFERED_OUTPUT_BYTES = 64 * 1024;
|
|
14
15
|
function resolveBashExecutable() {
|
|
15
16
|
const envCandidate = process.env.LOOP_AGENT_BASH ?? process.env.GIT_BASH;
|
|
16
17
|
if (envCandidate && existsSync(envCandidate))
|
|
@@ -85,8 +86,8 @@ export async function executeShellCommand(input) {
|
|
|
85
86
|
env: buildShellProcessEnv(input.envAllowlist, injectedEnv),
|
|
86
87
|
stdio: ["ignore", "pipe", "pipe"],
|
|
87
88
|
});
|
|
88
|
-
let stdout =
|
|
89
|
-
let stderr =
|
|
89
|
+
let stdout = createBoundedOutput();
|
|
90
|
+
let stderr = createBoundedOutput();
|
|
90
91
|
let timedOut = false;
|
|
91
92
|
let settled = false;
|
|
92
93
|
let timeoutHandle;
|
|
@@ -103,22 +104,36 @@ export async function executeShellCommand(input) {
|
|
|
103
104
|
command: input.command,
|
|
104
105
|
cwd: input.cwd,
|
|
105
106
|
durationMs: Date.now() - startedAt,
|
|
106
|
-
stdout,
|
|
107
|
-
|
|
107
|
+
stdout: formatBoundedOutput(stdout),
|
|
108
|
+
stdoutBytes: stdout.bytes,
|
|
109
|
+
stdoutTruncated: stdout.truncated,
|
|
110
|
+
stderr: formatBoundedOutput(stderr),
|
|
111
|
+
stderrBytes: stderr.bytes,
|
|
112
|
+
stderrTruncated: stderr.truncated,
|
|
113
|
+
...(input.outputArtifacts
|
|
114
|
+
? {
|
|
115
|
+
stdoutArtifactPath: input.outputArtifacts.stdoutPath,
|
|
116
|
+
stderrArtifactPath: input.outputArtifacts.stderrPath,
|
|
117
|
+
}
|
|
118
|
+
: {}),
|
|
108
119
|
timedOut,
|
|
109
120
|
...result,
|
|
110
121
|
});
|
|
111
122
|
}
|
|
112
123
|
child.stdout.setEncoding("utf-8");
|
|
113
124
|
child.stderr.setEncoding("utf-8");
|
|
125
|
+
initializeOutputArtifacts(input.outputArtifacts);
|
|
114
126
|
child.stdout.on("data", (chunk) => {
|
|
115
|
-
stdout
|
|
127
|
+
stdout = appendBoundedOutput(stdout, chunk);
|
|
128
|
+
appendOutputArtifact(input.outputArtifacts?.stdoutPath, chunk);
|
|
116
129
|
});
|
|
117
130
|
child.stderr.on("data", (chunk) => {
|
|
118
|
-
stderr
|
|
131
|
+
stderr = appendBoundedOutput(stderr, chunk);
|
|
132
|
+
appendOutputArtifact(input.outputArtifacts?.stderrPath, chunk);
|
|
119
133
|
});
|
|
120
134
|
child.on("error", (error) => {
|
|
121
|
-
stderr
|
|
135
|
+
stderr = appendBoundedOutput(stderr, `${stderr.text ? "\n" : ""}${error.message}`);
|
|
136
|
+
appendOutputArtifact(input.outputArtifacts?.stderrPath, error.message);
|
|
122
137
|
finish({
|
|
123
138
|
exitCode: null,
|
|
124
139
|
failureCategory: "spawn-error",
|
|
@@ -149,6 +164,46 @@ export async function executeShellCommand(input) {
|
|
|
149
164
|
}
|
|
150
165
|
});
|
|
151
166
|
}
|
|
167
|
+
function createBoundedOutput() {
|
|
168
|
+
return { bytes: 0, text: "", truncated: false };
|
|
169
|
+
}
|
|
170
|
+
function appendBoundedOutput(current, chunk) {
|
|
171
|
+
const bytes = current.bytes + Buffer.byteLength(chunk);
|
|
172
|
+
const combined = current.text + chunk;
|
|
173
|
+
if (Buffer.byteLength(combined) <= MAX_BUFFERED_OUTPUT_BYTES) {
|
|
174
|
+
return { bytes, text: combined, truncated: current.truncated };
|
|
175
|
+
}
|
|
176
|
+
const tail = Buffer.from(combined)
|
|
177
|
+
.subarray(-MAX_BUFFERED_OUTPUT_BYTES)
|
|
178
|
+
.toString("utf-8")
|
|
179
|
+
.replace(/^\uFFFD/, "");
|
|
180
|
+
return { bytes, text: tail, truncated: true };
|
|
181
|
+
}
|
|
182
|
+
function formatBoundedOutput(output) {
|
|
183
|
+
return output.truncated ? `${output.text}\n...[truncated]` : output.text;
|
|
184
|
+
}
|
|
185
|
+
function initializeOutputArtifacts(paths) {
|
|
186
|
+
if (!paths)
|
|
187
|
+
return;
|
|
188
|
+
try {
|
|
189
|
+
mkdirSync(path.dirname(paths.stdoutPath), { recursive: true });
|
|
190
|
+
writeFileSync(paths.stdoutPath, "", "utf-8");
|
|
191
|
+
writeFileSync(paths.stderrPath, "", "utf-8");
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Artifact persistence is best-effort; the bounded result remains available.
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function appendOutputArtifact(filePath, chunk) {
|
|
198
|
+
if (!filePath)
|
|
199
|
+
return;
|
|
200
|
+
try {
|
|
201
|
+
appendFileSync(filePath, chunk, "utf-8");
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Keep command execution independent from a transient artifact write failure.
|
|
205
|
+
}
|
|
206
|
+
}
|
|
152
207
|
function summarizeCommandResults(results) {
|
|
153
208
|
const stdout = results
|
|
154
209
|
.map((result) => [`$ ${result.command}`, result.stdout.trim()].filter(Boolean).join("\n"))
|
|
@@ -195,11 +250,17 @@ export function buildShellResultSummaryMarkdown(input) {
|
|
|
195
250
|
lines.push("", `## Command ${index + 1}: ${result.command}`, "", "### stdout", "", "```text", stdout.text, "```");
|
|
196
251
|
if (stdout.truncated)
|
|
197
252
|
lines.push("", "_(stdout truncated)_");
|
|
253
|
+
if (result.stdoutArtifactPath) {
|
|
254
|
+
lines.push("", `Full stdout artifact: ${result.stdoutArtifactPath}`);
|
|
255
|
+
}
|
|
198
256
|
if (stderr.text.trim()) {
|
|
199
257
|
lines.push("", "### stderr", "", "```text", stderr.text, "```");
|
|
200
258
|
if (stderr.truncated)
|
|
201
259
|
lines.push("", "_(stderr truncated)_");
|
|
202
260
|
}
|
|
261
|
+
if (result.stderrArtifactPath) {
|
|
262
|
+
lines.push("", `Full stderr artifact: ${result.stderrArtifactPath}`);
|
|
263
|
+
}
|
|
203
264
|
}
|
|
204
265
|
return `${lines.join("\n")}\n`;
|
|
205
266
|
}
|
|
@@ -235,12 +296,17 @@ export async function executeDagShellNode(input, meta) {
|
|
|
235
296
|
beforeStatus = undefined;
|
|
236
297
|
}
|
|
237
298
|
for (const command of commands) {
|
|
299
|
+
const commandNumber = results.length + 1;
|
|
238
300
|
const result = await executeShellCommand({
|
|
239
301
|
command,
|
|
240
302
|
cwd,
|
|
241
303
|
timeoutMs,
|
|
242
304
|
envAllowlist: shell.envAllowlist,
|
|
243
305
|
dagRunMeta: { runDir: meta.runDir, runId: meta.runId },
|
|
306
|
+
outputArtifacts: {
|
|
307
|
+
stdoutPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stdout.txt`),
|
|
308
|
+
stderrPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stderr.txt`),
|
|
309
|
+
},
|
|
244
310
|
});
|
|
245
311
|
results.push(result);
|
|
246
312
|
if (!result.ok)
|
|
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { repoRelativePath } from '../shared/path-refs.js';
|
|
4
4
|
import { loadHarnessManifest } from './harness.js';
|
|
5
5
|
import { auditLoopAgentSkillSafety } from './skill-safety.js';
|
|
6
|
-
import { getTaskPaths, getTaskStatus, loadTaskConfig
|
|
6
|
+
import { getTaskPaths, getTaskStatus, loadTaskConfig } from '../task/runtime.js';
|
|
7
7
|
import { buildTaskReadModel } from '../task/read-model.js';
|
|
8
8
|
const SKIP_DIRS = new Set([
|
|
9
9
|
'.git',
|
|
@@ -244,12 +244,15 @@ export async function checkTaskHandoff(repoRoot, taskId) {
|
|
|
244
244
|
message: '任务已 completed,但验证工件未标记完成',
|
|
245
245
|
});
|
|
246
246
|
}
|
|
247
|
-
|
|
247
|
+
// Standard DAG tasks do not require a retrospective artifact. Preserve an
|
|
248
|
+
// explicit legacy task contract, however, so historical tasks that opted
|
|
249
|
+
// into requireRetrospective do not silently lose their handoff warning.
|
|
250
|
+
if (state.status === 'completed' && taskConfig.requireRetrospective && !state.artifacts.retrospective) {
|
|
248
251
|
issues.push({
|
|
249
252
|
type: 'missing-retrospective-artifact',
|
|
250
253
|
severity: 'warning',
|
|
251
254
|
file: toRepoRelative(repoRoot, path.join(taskPaths.taskDir, 'artifacts', '复盘报告.md')),
|
|
252
|
-
message: '
|
|
255
|
+
message: '任务显式要求 retrospective,但复盘工件未完成',
|
|
253
256
|
});
|
|
254
257
|
}
|
|
255
258
|
if (readModel.promotion.required && !readModel.promotion.ready) {
|
|
@@ -16,7 +16,8 @@ export const complexityRouteSchema = z.object({
|
|
|
16
16
|
export const worktreeManifestConfigSchema = z.object({
|
|
17
17
|
rootRelativePath: z.string().optional().default(".worktrees"),
|
|
18
18
|
});
|
|
19
|
-
export const taskExecutorSchema = z.enum(["pi"
|
|
19
|
+
export const taskExecutorSchema = z.enum(["pi"]);
|
|
20
|
+
export const CURSOR_TASK_EXECUTOR_REMOVED_ERROR = 'task executor "cursor" is no longer supported; governed runtime is Pi-only';
|
|
20
21
|
export const executorManifestSchema = z.object({
|
|
21
22
|
description: z.string().optional(),
|
|
22
23
|
enabled: z.boolean().optional(),
|
|
@@ -36,12 +37,16 @@ export const workflowPolicyProfileNameSchema = z.enum([
|
|
|
36
37
|
"standard",
|
|
37
38
|
"reviewed",
|
|
38
39
|
"supervised",
|
|
40
|
+
"backend-test",
|
|
39
41
|
]);
|
|
40
42
|
export const workflowPolicyDagTemplateSchema = z.enum([
|
|
41
43
|
"standard-dag",
|
|
42
44
|
"review-gated-dag",
|
|
43
45
|
"supervised-implementation",
|
|
46
|
+
"frontend-implementation",
|
|
47
|
+
"backend-test-dag",
|
|
44
48
|
]);
|
|
49
|
+
export const workflowPolicyOutputLanguageSchema = z.enum(["zh-CN", "en"]);
|
|
45
50
|
export const workflowPolicySchema = z
|
|
46
51
|
.object({
|
|
47
52
|
/** Preferred implementation workflow for recoverable loop-agent work. Declarative policy; callers may still require explicit CLI flags. */
|
|
@@ -51,6 +56,9 @@ export const workflowPolicySchema = z
|
|
|
51
56
|
.default("agent-dag"),
|
|
52
57
|
dag: z
|
|
53
58
|
.object({
|
|
59
|
+
outputLanguage: workflowPolicyOutputLanguageSchema
|
|
60
|
+
.optional()
|
|
61
|
+
.default("zh-CN"),
|
|
54
62
|
defaultEntry: z
|
|
55
63
|
.enum(["dag run-task", "run-dag"])
|
|
56
64
|
.optional()
|
|
@@ -69,6 +77,9 @@ export const workflowPolicySchema = z
|
|
|
69
77
|
supervised: workflowPolicyDagTemplateSchema
|
|
70
78
|
.optional()
|
|
71
79
|
.default("supervised-implementation"),
|
|
80
|
+
"backend-test": workflowPolicyDagTemplateSchema
|
|
81
|
+
.optional()
|
|
82
|
+
.default("backend-test-dag"),
|
|
72
83
|
})
|
|
73
84
|
.optional()
|
|
74
85
|
.default({}),
|
|
@@ -88,7 +99,9 @@ export const workflowPolicySchema = z
|
|
|
88
99
|
})
|
|
89
100
|
.optional()
|
|
90
101
|
.default({});
|
|
91
|
-
export const
|
|
102
|
+
export const CURSOR_HARNESS_EXECUTOR_REMOVED_ERROR = 'harness executors.cursor is no longer supported; remove it and use executors.pi only (Cursor is available only via cursor-prompt sidecar)';
|
|
103
|
+
export const harnessManifestSchema = z
|
|
104
|
+
.object({
|
|
92
105
|
version: z.number(),
|
|
93
106
|
project: z.string(),
|
|
94
107
|
adapter: z.string().optional(),
|
|
@@ -134,4 +147,22 @@ export const harnessManifestSchema = z.object({
|
|
|
134
147
|
.record(z.string(), executorManifestSchema)
|
|
135
148
|
.optional()
|
|
136
149
|
.default({}),
|
|
150
|
+
})
|
|
151
|
+
.superRefine((manifest, ctx) => {
|
|
152
|
+
const executors = manifest.executors ?? {};
|
|
153
|
+
if ("cursor" in executors) {
|
|
154
|
+
ctx.addIssue({
|
|
155
|
+
code: z.ZodIssueCode.custom,
|
|
156
|
+
message: CURSOR_HARNESS_EXECUTOR_REMOVED_ERROR,
|
|
157
|
+
path: ["executors", "cursor"],
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const entrypoints = manifest.entrypoints ?? {};
|
|
161
|
+
if ("cursorExecutorUsage" in entrypoints) {
|
|
162
|
+
ctx.addIssue({
|
|
163
|
+
code: z.ZodIssueCode.custom,
|
|
164
|
+
message: 'entrypoints.cursorExecutorUsage is no longer supported; remove it (Cursor is cursor-prompt sidecar only)',
|
|
165
|
+
path: ["entrypoints", "cursorExecutorUsage"],
|
|
166
|
+
});
|
|
167
|
+
}
|
|
137
168
|
});
|
|
@@ -9,9 +9,6 @@ function loopActionWriteOptions(repoRoot) {
|
|
|
9
9
|
export async function writeLoopShellVerifyRecord(repoRoot, taskId, round, record) {
|
|
10
10
|
await writeJsonAtomic(loopActionFile(repoRoot, taskId, "verification", `round-${round}.json`), record, loopActionWriteOptions(repoRoot));
|
|
11
11
|
}
|
|
12
|
-
export async function writeLoopCursorFixRecord(repoRoot, taskId, round, record) {
|
|
13
|
-
await writeJsonAtomic(loopActionFile(repoRoot, taskId, "reviews", `round-${round}-cursor-fix.json`), record, loopActionWriteOptions(repoRoot));
|
|
14
|
-
}
|
|
15
12
|
export async function writeLoopDagRecord(repoRoot, taskId, round, record) {
|
|
16
13
|
await writeJsonAtomic(loopActionFile(repoRoot, taskId, "dag", `round-${round}.json`), record, loopActionWriteOptions(repoRoot));
|
|
17
14
|
}
|