@tea-agent/loop-agent 0.24.11-beta.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -4
- package/README.md +4 -4
- package/dist/application/dag/generate-task-dag.js +25 -8
- package/dist/commands/dag-init-hybrid.js +3 -2
- package/dist/commands/delegate.js +11 -11
- package/dist/commands/init.js +29 -14
- package/dist/commands/instructions.js +1 -2
- package/dist/executors/dag-pi-executor.js +74 -13
- package/dist/executors/pi-executor.js +25 -7
- package/dist/executors/pi-prompt-transport.js +198 -0
- package/dist/executors/pi-sdk-executor.js +1 -1
- package/dist/executors/process-tree.js +33 -0
- package/dist/executors/shell-executor.js +362 -24
- package/dist/executors/shell-verification.js +4 -2
- package/dist/infrastructure/harness/task-store.js +31 -0
- package/dist/shared/operator/capabilities.js +6 -0
- package/dist/task/read-model.js +3 -7
- package/dist/task/runtime.js +12 -3
- package/dist/verification/maven/cache.js +142 -0
- package/dist/verification/maven/index.js +120 -0
- package/dist/verification/maven/plan-commands.js +421 -0
- package/dist/verification/maven/pom-static.js +136 -0
- package/dist/verification/maven/scope-resolve.js +153 -0
- package/dist/verification/maven/stale.js +130 -0
- package/dist/verification/maven/types.js +23 -0
- package/dist/verification/maven/workspace-graph.js +322 -0
- package/dist/worker/cli.js +236 -21
- package/dist/worker/delivery/final-verification.js +12 -0
- package/dist/worker/delivery/git-transaction.js +32 -7
- package/dist/worker/delivery/package.js +9 -5
- package/dist/worker/delivery/verification-bundle.js +26 -5
- package/dist/worker/feature/advance.js +301 -0
- package/dist/worker/feature/doctor.js +223 -0
- package/dist/worker/feature/next-action.js +11 -3
- package/dist/worker/feature/scaffold.js +798 -0
- package/dist/worker/observe/dag-run-artifacts.js +90 -0
- package/dist/worker/observe/node-input.js +444 -0
- package/dist/worker/observe/routes.js +17 -0
- package/dist/worker/observe/static/api.js +9 -0
- package/dist/worker/observe/static/constants.js +9 -0
- package/dist/worker/observe/static/state.js +14 -0
- package/dist/worker/observe/static/styles.css +74 -0
- package/dist/worker/observe/static/views/dag-inspector.js +371 -15
- package/dist/worker/outcomes/projector.js +5 -1
- package/dist/worker/run-task/run-task.js +18 -2
- package/dist/worker/runner/run-ready.js +41 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +300 -9
- package/dist/workflows/dag/backend-test-result-contract.js +103 -0
- package/dist/workflows/dag/failure-category.js +5 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +4 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +0 -17
- package/dist/workflows/dag/frontend-test-result-contract.js +106 -41
- package/dist/workflows/dag/init-hybrid.js +224 -39
- package/dist/workflows/dag/node-execution.js +3 -2
- package/dist/workflows/dag/reconcile-run.js +24 -0
- package/dist/workflows/dag/types.js +57 -0
- package/dist/workflows/dag/validate.js +19 -2
- package/dist/workflows/loop/actions/dag-action.js +2 -1
- package/docs/architecture/dag-execution.md +5 -1
- package/docs/architecture/runtime-boundaries.md +11 -1
- package/docs/architecture/worker-and-feature.md +2 -0
- package/docs/init-surface.manifest.json +3 -0
- package/docs/templates/backend-test-dag.json +3 -3
- package/docs/templates/product-line/README.md +17 -1
- package/docs/templates/product-line/feature-scaffold-batch.example.yaml +31 -0
- package/docs/templates/product-line/scaffold-samples/README.md +36 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/acceptance.yaml +13 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/feature.yaml +3 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/BE-IMPL-001.yaml +83 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/task-graph.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/acceptance.yaml +15 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/design.md +18 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/feature.yaml +3 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/BE-IMPL-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/CONTRACT-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/FE-IMPL-001.yaml +86 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/task-graph.yaml +40 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/acceptance.yaml +13 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/feature.yaml +3 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/FE-IMPL-001.yaml +85 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/task-graph.yaml +14 -0
- package/examples/l5-report-coms-process-definition.html +322 -0
- package/package.json +1 -1
- package/skills/agent-worker/references/agent-worker-operator.md +84 -0
- package/skills/frontend-implementation/references/node-contracts.md +2 -2
- package/skills/loop-agent/SKILL.md +14 -16
- package/skills/loop-agent/references/command-reference.md +33 -12
- package/skills/loop-agent/references/harness-policy.md +28 -6
- package/skills/loop-agent/references/hybrid-dag.md +8 -7
- package/skills/loop-agent/references/post-implementation-and-patterns.md +19 -9
- package/skills/loop-agent/references/source-and-plan-practice.md +161 -0
- package/skills/loop-agent/references/task-workflow.md +13 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,15 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.25.1] - 2026-07-30
|
|
6
|
+
|
|
7
|
+
### 重点更新
|
|
8
|
+
|
|
9
|
+
- 后端测试 DAG 节点新增 JaCoCo 覆盖率收集能力,支持在 L-5 dashboard 展示 Java 后端的代码行与分支覆盖率
|
|
10
|
+
- 规范任务 DAG 草稿的默认输出路径,统一指向 `.harness/tasks/<task-id>/dag.json`
|
|
11
|
+
- 修复 loop-agent 技能条目因注入预算超限被截断的问题,确保默认 DAG 下技能完整解析
|
|
12
|
+
|
|
13
|
+
### 新增
|
|
14
|
+
|
|
15
|
+
- backend-test DAG 节点 7 支持可选的 JaCoCo 覆盖率收集:通过跨网络 TCP dump JaCoCo tcpserver 数据,经 jacococli.jar 转换并解析为 code-coverage-v1 合约,供 L-5 dashboard 使用
|
|
16
|
+
- task shell 配置新增 `jacocoCoverage` 字段,支持配置 endpoint、cliJarPath、includes 和 connectTimeoutMs
|
|
17
|
+
- 新增面向开发与运维的 JaCoCo 覆盖率部署指南,涵盖 tcpserver agent 挂载、端口放行、cli 预装及安全提醒
|
|
18
|
+
|
|
5
19
|
### 改进
|
|
6
20
|
|
|
7
|
-
-
|
|
21
|
+
- `dag run-task` 与 `dag init-hybrid` 的默认输出路径改为 `.harness/tasks/<task-id>/dag.json`,不再写入系统临时目录
|
|
22
|
+
- agent-worker 在 strict validate 成功后,将校验通过的草稿原子同步到 task 根 `dag.json`
|
|
23
|
+
- task read-model、delegate worktree 提示及各文档示例统一指向规范化的任务草稿路径
|
|
24
|
+
- skills 补充 `source-and-plan-practice.md`,明确 `import-prd` 与 `plan create` 的决策表与推荐顺序,不强制微小任务
|
|
25
|
+
|
|
26
|
+
### 修复
|
|
27
|
+
|
|
28
|
+
- 压缩 SKILL.md 及 required-reference 体积,修复默认 DAG 技能注入预算超限导致 loop-agent 条目被截断的问题
|
|
8
29
|
|
|
9
|
-
|
|
30
|
+
## [0.25.0] - 2026-07-30
|
|
10
31
|
|
|
11
|
-
|
|
12
|
-
|
|
32
|
+
### 重点更新
|
|
33
|
+
|
|
34
|
+
- 新增确定性 Feature Packet 脚手架,支持后端、前端及含 API 前端模板,并具备原子批量写入与预检能力
|
|
35
|
+
- 新增 Feature 交付编排命令,自动串联最终验证、交付与收口流程
|
|
36
|
+
- 显著提升 DAG 执行稳定性,修复超时残留子进程、重复验证及耗时记录不完整等问题
|
|
37
|
+
- 新增任务级 Maven 验证规划,自动定位并仅针对相关模块执行验证
|
|
38
|
+
- 后端测试节点现可自动生成指标机器计算的自包含 L-5 可视化报告
|
|
39
|
+
|
|
40
|
+
### 新增
|
|
41
|
+
|
|
42
|
+
- backend-test DAG 节点 7 支持可选的 JaCoCo 覆盖率收集(Java 服务):task shell 配置新增 `jacocoCoverage`(endpoint + cliJarPath + includes + connectTimeoutMs);节点 7 在 pytest 后跨网络 TCP dump JaCoCo tcpserver 执行数据,用 jacococli.jar 转为 jacoco.xml,确定性解析为 `contracts/code-coverage-v1.json` 并喂给 L-5 dashboard 的 line/branch 指标;全程失败安全(endpoint 不通 / cli 缺失 / 解析失败 → 覆盖率降级 unavailable,不阻断测试与 L-5)
|
|
43
|
+
- 提供 agent-worker Feature Packet 脚手架,内置 backend-only、frontend-only 和 fe-with-api 模板,支持原子批量写入与 validate-feature 预检
|
|
44
|
+
- 新增 agent-worker feature advance 命令,编排 verify-final、delivery 到 closeout 的完整交付路径
|
|
45
|
+
- 新增 agent-worker feature doctor 命令,提供只读的交付就绪诊断(覆盖证据、Pool 状态与工作区改动)
|
|
46
|
+
- Maven 验证现支持任务级范围规划,根据任务涉及的目录自动定位并验证正确的 POM 模块
|
|
47
|
+
- 后端测试 DAG 节点 7 现可确定性生成美观的自包含 L-5 可视化报告,指标完全由机器计算
|
|
48
|
+
|
|
49
|
+
### 改进
|
|
50
|
+
|
|
51
|
+
- Feature Delivery 在省略特定参数时,默认使用 feature verify-final 写入的标准证据路径
|
|
52
|
+
- TaskConfig 持久化入口在写入前执行 schema 校验,拒绝不符合规范的数据格式
|
|
53
|
+
- Dogfood 前端测试服务器在检测到旧缓存导致接口数据异常时,现会自动强制重启恢复
|
|
54
|
+
|
|
55
|
+
### 修复
|
|
13
56
|
|
|
57
|
+
- 显著加固 DAG 执行可靠性,修复超时或中断后子进程残留问题,降低文件占用与测试干扰概率
|
|
58
|
+
- 修复受监督流程可能重复执行完整验证的问题,避免高成本检查在不同阶段反复运行
|
|
59
|
+
- 修复 DAG 被放弃或替换时,运行中节点实际耗时记录不完整的问题
|
|
60
|
+
- 修复 Windows 环境下因提示内容过长导致 DAG 无法启动的问题,现安全采用受控临时文件传输
|
|
61
|
+
- 修复前端实现 DAG 在未确认项目 Mock 能力时误报缺失 Mock 文件的问题
|
|
62
|
+
- 修复结构化任务配置中数组类型校验不严格的问题
|
|
14
63
|
|
|
15
64
|
## [0.24.10] - 2026-07-29
|
|
16
65
|
|
package/README.md
CHANGED
|
@@ -64,19 +64,19 @@ loop-agent new-task example-task "实现一个有明确验收标准的小功能"
|
|
|
64
64
|
loop-agent dag run-task example-task \
|
|
65
65
|
--profile auto \
|
|
66
66
|
--strict-models \
|
|
67
|
-
--output
|
|
67
|
+
--output .harness/tasks/example-task/dag.json
|
|
68
68
|
|
|
69
69
|
loop-agent dag validate \
|
|
70
|
-
--dag
|
|
70
|
+
--dag .harness/tasks/example-task/dag.json \
|
|
71
71
|
--strict-models \
|
|
72
72
|
--strict-governance
|
|
73
73
|
|
|
74
74
|
loop-agent run-dag \
|
|
75
|
-
--dag
|
|
75
|
+
--dag .harness/tasks/example-task/dag.json \
|
|
76
76
|
--cwd .
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
省略 `--output` 时默认写入 `.harness/tasks/<task-id>/dag.json`。详细步骤见[第一次运行](website/docs/quick-start/first-run.md)和 [Agent DAG 工作流](website/docs/guides/agent-dag.md)。
|
|
80
80
|
|
|
81
81
|
## 查看执行状态
|
|
82
82
|
|
|
@@ -7,10 +7,10 @@ import { parseDagSpec } from "../../workflows/dag/types.js";
|
|
|
7
7
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
8
8
|
import { loadHarnessManifest } from "../../governance/harness.js";
|
|
9
9
|
import { assertExecPlanIndexConsistent } from "../../governance/exec-plans.js";
|
|
10
|
-
import {
|
|
10
|
+
import { initHybridDagFromTask, } from "../../workflows/dag/init-hybrid.js";
|
|
11
11
|
import { validateDagUseCase } from "./validate-dag.js";
|
|
12
12
|
import { runDagUseCase } from "./run-dag.js";
|
|
13
|
-
import { loadTaskConfig } from "../../task/runtime.js";
|
|
13
|
+
import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
|
|
14
14
|
import { writeTaskConfig } from "../../infrastructure/harness/task-store.js";
|
|
15
15
|
const PLACEHOLDER_WRITESET_MARKER = "REPLACE/WITH";
|
|
16
16
|
function buildValidateInput(repoRoot, dagPath, parsed) {
|
|
@@ -41,7 +41,15 @@ function isUnsafeWriteSetEntry(entry) {
|
|
|
41
41
|
return normalized.includes(PLACEHOLDER_WRITESET_MARKER);
|
|
42
42
|
}
|
|
43
43
|
export async function assertSafeForExecution(dagPath) {
|
|
44
|
-
|
|
44
|
+
let raw;
|
|
45
|
+
try {
|
|
46
|
+
const content = await readFile(dagPath, "utf-8");
|
|
47
|
+
raw = JSON.parse(content);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
throw new Error(`failed to read valid DAG JSON at ${dagPath}: ${message}`);
|
|
52
|
+
}
|
|
45
53
|
const unsafe = [];
|
|
46
54
|
for (const task of raw.tasks ?? []) {
|
|
47
55
|
if (task.writePolicy !== "exclusive")
|
|
@@ -126,7 +134,16 @@ function resolveReviewPacketShellCommands(task) {
|
|
|
126
134
|
return resolveShellCommands(task.shell);
|
|
127
135
|
}
|
|
128
136
|
async function buildReviewPacket(input) {
|
|
129
|
-
|
|
137
|
+
let raw;
|
|
138
|
+
try {
|
|
139
|
+
const content = await readFile(input.dagPath, "utf-8");
|
|
140
|
+
raw = JSON.parse(content);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
144
|
+
throw new Error(`failed to read valid DAG JSON at ${input.dagPath}: ${message}`);
|
|
145
|
+
}
|
|
146
|
+
const spec = parseDagSpec(raw);
|
|
130
147
|
const writers = collectWriterTasksForPacket(spec).map((task) => {
|
|
131
148
|
const writeSet = task.writeSet ?? [];
|
|
132
149
|
return {
|
|
@@ -247,8 +264,7 @@ export async function generateTaskDagUseCase(input) {
|
|
|
247
264
|
: "task-source";
|
|
248
265
|
profileRouting.routingReasons = candidateResult.templateSelection.reasons;
|
|
249
266
|
if (parsed.profile === "auto") {
|
|
250
|
-
profileRouting.selectedByProfile =
|
|
251
|
-
resolveAutoRoutingProfile(profileRouting.candidateProfile);
|
|
267
|
+
profileRouting.selectedByProfile = resolveAutoRoutingProfile(profileRouting.candidateProfile);
|
|
252
268
|
}
|
|
253
269
|
else if (parsed.profileExplicit) {
|
|
254
270
|
profileRouting.selectedByProfile = parsed.profile;
|
|
@@ -256,7 +272,8 @@ export async function generateTaskDagUseCase(input) {
|
|
|
256
272
|
}
|
|
257
273
|
// Persist the automatic business workflow route while preserving the
|
|
258
274
|
// governance profile as a separate routing dimension.
|
|
259
|
-
if (hasAutomaticTaskSourceRoute &&
|
|
275
|
+
if (hasAutomaticTaskSourceRoute &&
|
|
276
|
+
candidateResult.template === "frontend-implementation") {
|
|
260
277
|
const taskConfig = await loadTaskConfig(repoRoot, parsed.taskId);
|
|
261
278
|
if (!taskConfig.taskKind || taskConfig.taskKind === "standard") {
|
|
262
279
|
await writeTaskConfig(repoRoot, parsed.taskId, {
|
|
@@ -297,7 +314,7 @@ export async function generateTaskDagUseCase(input) {
|
|
|
297
314
|
ok: true,
|
|
298
315
|
taskId: initResult.taskId,
|
|
299
316
|
outputPath,
|
|
300
|
-
defaultOutputPath:
|
|
317
|
+
defaultOutputPath: getTaskPaths(repoRoot, parsed.taskId).dagDraftPath,
|
|
301
318
|
taskCount: initResult.taskCount,
|
|
302
319
|
nodeIds: initResult.nodeIds,
|
|
303
320
|
title: validateSummary.title,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { initHybridDagFromTask } from "../workflows/dag/init-hybrid.js";
|
|
3
|
+
import { getTaskPaths } from "../task/runtime.js";
|
|
3
4
|
export function parseDagInitHybridArgs(args) {
|
|
4
5
|
if (args.length === 0) {
|
|
5
6
|
throw new Error("usage: dag init-hybrid <task-id> [--output <path>]");
|
|
@@ -44,7 +45,7 @@ export async function runDagInitHybrid(repoRoot, rawArgs) {
|
|
|
44
45
|
ok: true,
|
|
45
46
|
taskId: result.taskId,
|
|
46
47
|
outputPath: result.outputPath,
|
|
47
|
-
defaultOutputPath:
|
|
48
|
+
defaultOutputPath: getTaskPaths(repoRoot, parsed.taskId).dagDraftPath,
|
|
48
49
|
taskCount: result.taskCount,
|
|
49
50
|
nodeIds: result.nodeIds,
|
|
50
51
|
next: [
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import os from "node:os";
|
|
2
|
-
import path from "node:path";
|
|
3
1
|
import { delegateTask } from "../task/delegate.js";
|
|
4
2
|
import { loadHarnessManifest } from "../governance/harness.js";
|
|
5
3
|
import { generateTaskDagUseCase } from "../application/dag/generate-task-dag.js";
|
|
6
4
|
import { validateDagUseCase } from "../application/dag/validate-dag.js";
|
|
7
5
|
import { runDagUseCase } from "../application/dag/run-dag.js";
|
|
6
|
+
import { getTaskPaths } from "../task/runtime.js";
|
|
8
7
|
const DELEGATE_USAGE = "usage: delegate <taskId> [--base <branch>] [--branch <name>] [--no-symlink] [--auto-run]";
|
|
9
8
|
export function parseDelegateArgs(args) {
|
|
10
9
|
if (args.length === 0) {
|
|
@@ -59,12 +58,13 @@ export function parseDelegateArgs(args) {
|
|
|
59
58
|
return { taskId, base, branch, symlinkNodeModules, autoRun };
|
|
60
59
|
}
|
|
61
60
|
export function printDelegateNextSteps(taskId, result) {
|
|
62
|
-
const dagPath =
|
|
61
|
+
const dagPath = getTaskPaths(result.worktreePath, taskId).dagDraftPath;
|
|
63
62
|
const lines = [
|
|
64
63
|
`[delegate] worktree ready (Pi-only).`,
|
|
65
64
|
` worktree: ${result.worktreePath}`,
|
|
66
65
|
` Next — generate, validate, and run DAG in the worktree:`,
|
|
67
|
-
`
|
|
66
|
+
` cd ${JSON.stringify(result.worktreePath)}`,
|
|
67
|
+
` loop-agent --repo-root ${JSON.stringify(result.worktreePath)} dag run-task ${taskId} --profile auto --strict-models`,
|
|
68
68
|
` loop-agent dag validate --dag ${JSON.stringify(dagPath)} --strict-models --strict-governance`,
|
|
69
69
|
` loop-agent run-dag --dag ${JSON.stringify(dagPath)} --cwd ${JSON.stringify(result.worktreePath)}`,
|
|
70
70
|
` Or: loop-agent delegate ${taskId} --auto-run`,
|
|
@@ -89,30 +89,30 @@ export async function runDelegate(repoRoot, rawArgs, dependencies = DEFAULT_RUN_
|
|
|
89
89
|
symlinkNodeModules: parsed.symlinkNodeModules,
|
|
90
90
|
});
|
|
91
91
|
if (parsed.autoRun) {
|
|
92
|
-
const
|
|
92
|
+
const worktreeRoot = result.worktreePath;
|
|
93
|
+
const dagPath = getTaskPaths(worktreeRoot, parsed.taskId).dagDraftPath;
|
|
93
94
|
await dependencies.generateTaskDag({
|
|
94
|
-
repoRoot,
|
|
95
|
+
repoRoot: worktreeRoot,
|
|
95
96
|
taskId: parsed.taskId,
|
|
96
|
-
outputPath: dagPath,
|
|
97
97
|
strictModels: true,
|
|
98
98
|
execute: false,
|
|
99
99
|
initOnly: false,
|
|
100
100
|
dryRun: false,
|
|
101
|
-
cwd:
|
|
101
|
+
cwd: worktreeRoot,
|
|
102
102
|
profile: "auto",
|
|
103
103
|
profileExplicit: false,
|
|
104
104
|
});
|
|
105
105
|
await dependencies.validateDag({
|
|
106
|
-
repoRoot,
|
|
106
|
+
repoRoot: worktreeRoot,
|
|
107
107
|
dagPath,
|
|
108
108
|
strictModelMatrix: true,
|
|
109
109
|
strictGovernance: true,
|
|
110
110
|
forbidExecutors: [],
|
|
111
111
|
});
|
|
112
112
|
const runSummary = await dependencies.runDag({
|
|
113
|
-
repoRoot,
|
|
113
|
+
repoRoot: worktreeRoot,
|
|
114
114
|
dagPath,
|
|
115
|
-
cwd:
|
|
115
|
+
cwd: worktreeRoot,
|
|
116
116
|
initOnly: false,
|
|
117
117
|
dryRun: false,
|
|
118
118
|
});
|
package/dist/commands/init.js
CHANGED
|
@@ -345,7 +345,7 @@ check_ts_import_boundary() {
|
|
|
345
345
|
local label="$3"
|
|
346
346
|
[[ -d "\${from_dir}" && -d "\${forbidden_dir}" ]] || return 0
|
|
347
347
|
while IFS= read -r -d '' file; do
|
|
348
|
-
if grep -Eq "from ['
|
|
348
|
+
if grep -Eq "from ['\\"][^'\\"]*\${forbidden_dir}/|import\\(['\\"][^'\\"]*\${forbidden_dir}/|require\\(['\\"][^'\\"]*\${forbidden_dir}/" "\${file}"; then
|
|
349
349
|
violations+=("\${label}: \${file} imports \${forbidden_dir}")
|
|
350
350
|
fi
|
|
351
351
|
done < <(find "\${from_dir}" -type f \\( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.mjs' -o -name '*.cjs' \\) -print0)
|
|
@@ -412,9 +412,22 @@ check_skill "loop-agent" \\
|
|
|
412
412
|
"references/harness-policy.md" \\
|
|
413
413
|
"references/hybrid-dag.md" \\
|
|
414
414
|
"references/verification-and-failure-handling.md" \\
|
|
415
|
-
"references/command-reference.md"
|
|
415
|
+
"references/command-reference.md" \\
|
|
416
|
+
"references/source-and-plan-practice.md"
|
|
416
417
|
check_skill "agent-worker" "references/agent-worker-operator.md"
|
|
417
418
|
|
|
419
|
+
# loop-agent description is a YAML folded block (>-); grep ^description: only
|
|
420
|
+
# captures the indicator line. Validate the general-demand trigger terms
|
|
421
|
+
# against the whole skill file so host auto-discovery phrasing cannot drift.
|
|
422
|
+
loop_agent_skill=".agents/skills/loop-agent/SKILL.md"
|
|
423
|
+
loop_agent_text="$(tr '[:upper:]' '[:lower:]' < "\${loop_agent_skill}")"
|
|
424
|
+
for term in "loop-agent 帮我完成" "帮我实现" "帮我修复" "帮我开发" "agent dag" "new-task" "dag run-task" "dag validate" "run-dag"; do
|
|
425
|
+
if [[ "\${loop_agent_text}" != *"\${term}"* ]]; then
|
|
426
|
+
echo "skill entry 检查失败(loop-agent):description 缺少触发/路由词 \${term}" >&2
|
|
427
|
+
exit 1
|
|
428
|
+
fi
|
|
429
|
+
done
|
|
430
|
+
|
|
418
431
|
description_line="$(grep -E '^description:' .agents/skills/agent-worker/SKILL.md | head -n 1 | tr '[:upper:]' '[:lower:]')"
|
|
419
432
|
for term in "agent-worker" "feature packet" "taskspec" "task pool" "self-host" "candidate" "loop-agent"; do
|
|
420
433
|
if [[ "\${description_line}" != *"\${term}"* ]]; then
|
|
@@ -802,6 +815,7 @@ function buildManagedAgentsBlock(input) {
|
|
|
802
815
|
"| 初始化更新校验 / 检查初始化更新 / loop-agent 初始化更新校验 / loop agent初始化更新校验 | 更新校验 | 只读报告,不写入 |",
|
|
803
816
|
"| 初始化对齐 / 升级后对齐 / reconcile 初始化 / loop-agent 初始化对齐 | 升级对齐 | 自动应用确定性安全动作;活跃 DAG/Worker 或人工决策时零写入 |",
|
|
804
817
|
"| 初始化安全更新 / 应用初始化更新 / loop-agent 初始化安全更新 / loop agent初始化安全更新 | 安全更新 | 先 check-update,再只执行确定性安全动作 |",
|
|
818
|
+
"| loop-agent 帮我完成 / 帮我实现 / 帮我修复 / 帮我开发 <需求>;使用 loop-agent 完成 <X>;按 loop-agent 流程处理 <X> | 通用需求实现 | 先建任务源与 `task.json.allowedPaths` / `forbiddenPaths`,再 `new-task` → `dag run-task` → `dag validate` → `run-dag`;主会话不得直接修改业务实现 |",
|
|
805
819
|
"",
|
|
806
820
|
"**更新校验(只读)**:只读执行下面命令;**不得自动**执行 `apply-safe` 或模型合并。",
|
|
807
821
|
"",
|
|
@@ -843,9 +857,9 @@ function buildManagedAgentsBlock(input) {
|
|
|
843
857
|
"loop-agent new-task <task-id> \"任务标题\"",
|
|
844
858
|
"# write .harness/tasks/<task-id>/source/需求.md",
|
|
845
859
|
"# write .harness/tasks/<task-id>/source/执行约束.md",
|
|
846
|
-
"loop-agent dag run-task <task-id> --profile auto --strict-models
|
|
847
|
-
"loop-agent dag validate --dag
|
|
848
|
-
"loop-agent run-dag --dag
|
|
860
|
+
"loop-agent dag run-task <task-id> --profile auto --strict-models",
|
|
861
|
+
"loop-agent dag validate --dag .harness/tasks/<task-id>/dag.json --strict-models --strict-governance",
|
|
862
|
+
"loop-agent run-dag --dag .harness/tasks/<task-id>/dag.json --cwd .",
|
|
849
863
|
"```",
|
|
850
864
|
"",
|
|
851
865
|
"`source/需求.md` 与 `source/执行约束.md` 必需;写入前同步 `task.json.allowedPaths` / `task.json.forbiddenPaths` 并审查 writer `writeSet`。",
|
|
@@ -861,10 +875,11 @@ function buildManagedAgentsBlock(input) {
|
|
|
861
875
|
"### 运行看板(只读)",
|
|
862
876
|
"",
|
|
863
877
|
"```bash",
|
|
864
|
-
"agent-worker console
|
|
878
|
+
"agent-worker console # 默认 repo=当前目录,port=8790",
|
|
879
|
+
"agent-worker console serve --repo . --port 8790 # 兼容入口,等价于上面裸入口",
|
|
865
880
|
"```",
|
|
866
881
|
"",
|
|
867
|
-
"浏览器打开 `http://127.0.0.1:8790/`;检视面为 `http://127.0.0.1:8790/inspect/`。默认绑定本机 `127.0.0.1
|
|
882
|
+
"浏览器打开 `http://127.0.0.1:8790/`;检视面为 `http://127.0.0.1:8790/inspect/`。默认绑定本机 `127.0.0.1`;不要直接暴露到公开网络。端口被占用时不会自动更换,请用 `--port <port>` 显式指定。`agent-worker observe serve` 仅为兼容入口。",
|
|
868
883
|
"",
|
|
869
884
|
"### DAG 诊断与收口",
|
|
870
885
|
"",
|
|
@@ -1804,9 +1819,9 @@ function buildManagedReadmeBlock(input) {
|
|
|
1804
1819
|
"loop-agent new-task <task-id> \"任务标题\"",
|
|
1805
1820
|
"# write .harness/tasks/<task-id>/source/需求.md",
|
|
1806
1821
|
"# write .harness/tasks/<task-id>/source/执行约束.md",
|
|
1807
|
-
"loop-agent dag run-task <task-id> --profile auto --strict-models
|
|
1808
|
-
"loop-agent dag validate --dag
|
|
1809
|
-
"loop-agent run-dag --dag
|
|
1822
|
+
"loop-agent dag run-task <task-id> --profile auto --strict-models",
|
|
1823
|
+
"loop-agent dag validate --dag .harness/tasks/<task-id>/dag.json --strict-models --strict-governance",
|
|
1824
|
+
"loop-agent run-dag --dag .harness/tasks/<task-id>/dag.json --cwd .",
|
|
1810
1825
|
"```",
|
|
1811
1826
|
"",
|
|
1812
1827
|
`详细工作流见 \`${input.governanceRoot}/feature-workflow.md\`;验证矩阵见 \`${input.governanceRoot}/verification-matrix.md\`。任务 source 是必需项:\`source/需求.md\` 写目标、范围、非目标、验收标准和相关链接,\`source/执行约束.md\` 写允许路径、禁止路径、受保护变更、不变量、预期验证和失败条件。`,
|
|
@@ -2036,9 +2051,9 @@ function buildTargetFeatureWorkflow(input) {
|
|
|
2036
2051
|
"loop-agent new-task <task-id> \"Task title\"",
|
|
2037
2052
|
"# write .harness/tasks/<task-id>/source/需求.md",
|
|
2038
2053
|
"# write .harness/tasks/<task-id>/source/执行约束.md",
|
|
2039
|
-
"loop-agent dag run-task <task-id> --profile auto --strict-models
|
|
2040
|
-
"loop-agent dag validate --dag
|
|
2041
|
-
"loop-agent run-dag --dag
|
|
2054
|
+
"loop-agent dag run-task <task-id> --profile auto --strict-models",
|
|
2055
|
+
"loop-agent dag validate --dag .harness/tasks/<task-id>/dag.json --strict-models --strict-governance",
|
|
2056
|
+
"loop-agent run-dag --dag .harness/tasks/<task-id>/dag.json --cwd .",
|
|
2042
2057
|
"```",
|
|
2043
2058
|
"",
|
|
2044
2059
|
DAG_HARD_GATE_TRIGGER,
|
|
@@ -2660,7 +2675,7 @@ async function applySafeAction(input) {
|
|
|
2660
2675
|
if (!isRecord(harness) || !isRecord(harness.executors) || !isRecord(harness.executors.pi)) {
|
|
2661
2676
|
return false;
|
|
2662
2677
|
}
|
|
2663
|
-
if (!Object.
|
|
2678
|
+
if (!Object.hasOwn(harness.executors.pi, "requiresApiKey")) {
|
|
2664
2679
|
return false;
|
|
2665
2680
|
}
|
|
2666
2681
|
const nextPi = { ...harness.executors.pi };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { access, readdir } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { getArtifactPath, getArtifactRelativePath, } from "../shared/artifacts-core.js";
|
|
4
|
-
import { defaultHybridDagOutputPath } from "../workflows/dag/init-hybrid.js";
|
|
5
4
|
import { getTaskDir, getTaskPaths, loadTaskConfig } from "../task/runtime.js";
|
|
6
5
|
const STAGES = new Set([
|
|
7
6
|
"source",
|
|
@@ -96,7 +95,7 @@ export async function buildInstructions(repoRoot, stage, taskId) {
|
|
|
96
95
|
if (stage === "dag-draft") {
|
|
97
96
|
return refreshInstructionBlockers(withMissingDependencies({
|
|
98
97
|
...base,
|
|
99
|
-
outputPath:
|
|
98
|
+
outputPath: paths.dagDraftPath,
|
|
100
99
|
dependencies: [
|
|
101
100
|
paths.taskConfigPath,
|
|
102
101
|
path.join(paths.sourceDir, "需求.md"),
|
|
@@ -130,17 +130,23 @@ export function buildDagPiUserMessage(task, persona, step) {
|
|
|
130
130
|
const role = task.role ?? "unspecified";
|
|
131
131
|
const writePolicy = task.writePolicy ?? "read-only (default)";
|
|
132
132
|
if (isDagPiWriteTask(task)) {
|
|
133
|
+
const outcomeInstruction = task.writerOutcomePolicy
|
|
134
|
+
? "The first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed, IMPLEMENTATION_OUTCOME: already-satisfied, or IMPLEMENTATION_OUTCOME: blocked. Use changed only after producing a non-empty bounded diff; use already-satisfied only when the contract is already met and no file changed; use blocked when implementation cannot proceed."
|
|
135
|
+
: undefined;
|
|
133
136
|
return [
|
|
134
137
|
`You are executing hybrid DAG node "${task.id}" (role=${role}, piStep=${step}, writePolicy=${writePolicy}).`,
|
|
135
138
|
"The system prompt contains the full DAG envelope: objective, constraints, upstream context, and task.",
|
|
136
139
|
"Use write tools only for the bounded implementation requested by this node.",
|
|
140
|
+
outcomeInstruction,
|
|
137
141
|
`Allowed paths: ${(task.allowedPaths ?? []).join(", ") || "(none declared)"}.`,
|
|
138
142
|
`Write set: ${(task.writeSet ?? []).join(", ") || "(none declared)"}.`,
|
|
139
143
|
`Forbidden paths: ${(task.forbiddenPaths ?? []).join(", ") || "(none declared)"}.`,
|
|
140
144
|
"Preserve unrelated files. Do not edit outside allowedPaths/writeSet. Never write forbiddenPaths, .harness/dag-runs/**, or undeclared root artifacts/**.",
|
|
141
145
|
"Return concise Markdown with changed files, verification performed or still required, and residual risks.",
|
|
142
146
|
"Do not wrap the output in code fences and do not add conversational preamble.",
|
|
143
|
-
]
|
|
147
|
+
]
|
|
148
|
+
.filter((value) => Boolean(value))
|
|
149
|
+
.join(" ");
|
|
144
150
|
}
|
|
145
151
|
// Decision Gate nodes must emit a mandatory DECISION_ENVELOPE_JSON fenced block;
|
|
146
152
|
// do not inject the global no-fence instruction that would conflict with outputContract.
|
|
@@ -344,21 +350,39 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep) {
|
|
|
344
350
|
];
|
|
345
351
|
}
|
|
346
352
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
353
|
+
let writerOutcomeViolation;
|
|
354
|
+
if (mapped.ok && input.task.writerOutcomePolicy) {
|
|
355
|
+
if (changeManifestChangedFiles === undefined) {
|
|
356
|
+
writerOutcomeViolation =
|
|
357
|
+
"writer outcome validation failed: actual diff is unavailable";
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
const outcomeValidation = validateWriterImplementationOutcome(mapped.assistantText || mapped.stdout, changeManifestChangedFiles);
|
|
361
|
+
if (!outcomeValidation.ok) {
|
|
362
|
+
writerOutcomeViolation = outcomeValidation.reason;
|
|
363
|
+
}
|
|
357
364
|
}
|
|
365
|
+
}
|
|
366
|
+
if (writeGuardOk && beforeStatus !== undefined && changeManifestChangedFiles !== undefined) {
|
|
367
|
+
await persistWriterChangeManifest({
|
|
368
|
+
runDir: meta.runDir,
|
|
369
|
+
nodeId: input.task.id,
|
|
370
|
+
writerNodeId: input.task.id,
|
|
371
|
+
changedFiles: changeManifestChangedFiles,
|
|
372
|
+
beforeStatus,
|
|
373
|
+
afterStatus: changeManifestAfterStatus ?? "",
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
if (writeGuardOk && !writerOutcomeViolation) {
|
|
358
377
|
return mapped;
|
|
359
378
|
}
|
|
360
379
|
const stderrParts = [mapped.stderr];
|
|
361
|
-
|
|
380
|
+
if (!writeGuardOk) {
|
|
381
|
+
stderrParts.push(`write guard failed: ${writeGuardViolations.join(", ")}`);
|
|
382
|
+
}
|
|
383
|
+
if (writerOutcomeViolation) {
|
|
384
|
+
stderrParts.push(writerOutcomeViolation);
|
|
385
|
+
}
|
|
362
386
|
if (meta.writeGuardAttribution === "best-effort") {
|
|
363
387
|
stderrParts.push("write guard note: concurrent rank writers use best-effort per-node attribution; keep same-rank writeSet entries disjoint");
|
|
364
388
|
}
|
|
@@ -366,10 +390,47 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep) {
|
|
|
366
390
|
...mapped,
|
|
367
391
|
ok: false,
|
|
368
392
|
stderr: stderrParts.filter(Boolean).join("\n\n"),
|
|
369
|
-
failureCategory: mapped.ok
|
|
393
|
+
failureCategory: mapped.ok
|
|
394
|
+
? writeGuardOk
|
|
395
|
+
? "invalid-output"
|
|
396
|
+
: "write-guard"
|
|
397
|
+
: mapped.failureCategory,
|
|
370
398
|
durationMs: mapped.durationMs || Date.now() - started,
|
|
371
399
|
};
|
|
372
400
|
}
|
|
401
|
+
export function validateWriterImplementationOutcome(text, changedFiles) {
|
|
402
|
+
const firstLine = text
|
|
403
|
+
.split(/\r?\n/)
|
|
404
|
+
.map((line) => line.trim())
|
|
405
|
+
.find(Boolean);
|
|
406
|
+
const match = firstLine?.match(/^IMPLEMENTATION_OUTCOME: (changed|already-satisfied|blocked)$/);
|
|
407
|
+
if (!match) {
|
|
408
|
+
return {
|
|
409
|
+
ok: false,
|
|
410
|
+
reason: "writer outcome validation failed: missing or invalid first line; expected IMPLEMENTATION_OUTCOME: changed, IMPLEMENTATION_OUTCOME: already-satisfied, or IMPLEMENTATION_OUTCOME: blocked",
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
const outcome = match[1];
|
|
414
|
+
if (outcome === "blocked") {
|
|
415
|
+
return {
|
|
416
|
+
ok: false,
|
|
417
|
+
reason: "writer outcome validation failed: IMPLEMENTATION_OUTCOME: blocked cannot complete successfully",
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
if (outcome === "changed" && changedFiles.length === 0) {
|
|
421
|
+
return {
|
|
422
|
+
ok: false,
|
|
423
|
+
reason: "writer outcome validation failed: changed outcome has an empty diff",
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
if (outcome === "already-satisfied" && changedFiles.length > 0) {
|
|
427
|
+
return {
|
|
428
|
+
ok: false,
|
|
429
|
+
reason: "writer outcome validation failed: already-satisfied outcome has a non-empty diff",
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
return { ok: true, outcome };
|
|
433
|
+
}
|
|
373
434
|
export function mapPiResultToDagNodeResult(result, firstProtocolLine) {
|
|
374
435
|
const assistantText = canonicalizeProtocolFirstLine(result.assistantText, firstProtocolLine);
|
|
375
436
|
return {
|
|
@@ -3,6 +3,8 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { PI_REUSE_RUNTIME_ENV, isPiReuseRuntimeEffective, resolvePiReuseRuntimeMode as resolveConfiguredPiReuseRuntimeMode, } from './pi-runtime-reuse.js';
|
|
6
|
+
import { cleanupPiPromptTransport, createPiPromptTransport, } from './pi-prompt-transport.js';
|
|
7
|
+
import { processTreeSpawnOptions, terminateProcessTree } from './process-tree.js';
|
|
6
8
|
export function extractSubagentStats(stdout) {
|
|
7
9
|
let totalCalls = 0;
|
|
8
10
|
let failedCalls = 0;
|
|
@@ -507,12 +509,24 @@ function withCliBackend(result) {
|
|
|
507
509
|
}
|
|
508
510
|
/** CLI-only orchestration: primary attempt + optional fallback model retry. */
|
|
509
511
|
async function runCliOrchestration(options, meta) {
|
|
512
|
+
const promptTransport = createPiPromptTransport(options.prompt);
|
|
513
|
+
try {
|
|
514
|
+
return await runCliOrchestrationWithPromptFile(options, promptTransport.filePath, meta);
|
|
515
|
+
}
|
|
516
|
+
finally {
|
|
517
|
+
const cleanup = cleanupPiPromptTransport(promptTransport);
|
|
518
|
+
for (const warning of cleanup.warnings) {
|
|
519
|
+
process.stderr.write(`${warning}\n`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
async function runCliOrchestrationWithPromptFile(options, promptFilePath, meta) {
|
|
510
524
|
const primary = options.modelConfig ?? {};
|
|
511
525
|
const attemptedModels = [];
|
|
512
526
|
if (meta?.prependAttemptedModel) {
|
|
513
527
|
attemptedModels.push(meta.prependAttemptedModel);
|
|
514
528
|
}
|
|
515
|
-
const primaryResult = await executeSingleCliAttempt(options, primary);
|
|
529
|
+
const primaryResult = await executeSingleCliAttempt(options, primary, promptFilePath);
|
|
516
530
|
attemptedModels.push(primaryResult.modelDisplay);
|
|
517
531
|
let primaryIssues = [];
|
|
518
532
|
if (primaryResult.ok && options.validateOutput) {
|
|
@@ -525,7 +539,7 @@ async function runCliOrchestration(options, meta) {
|
|
|
525
539
|
&& shouldRetryWithFallback(primaryResult.stderr, primaryResult.stdout))
|
|
526
540
|
|| primaryInvalidOutput);
|
|
527
541
|
if (shouldFallback) {
|
|
528
|
-
const fallbackResult = await executeSingleCliAttempt(options, primary.fallback);
|
|
542
|
+
const fallbackResult = await executeSingleCliAttempt(options, primary.fallback, promptFilePath);
|
|
529
543
|
attemptedModels.push(fallbackResult.modelDisplay);
|
|
530
544
|
const combinedStderr = [primaryResult.stderr, fallbackResult.stderr].filter(Boolean).join('\n--- fallback ---\n');
|
|
531
545
|
const combinedStdout = [primaryResult.stdout, fallbackResult.stdout].filter(Boolean).join('\n--- fallback ---\n');
|
|
@@ -659,7 +673,7 @@ export async function executePiStep(options) {
|
|
|
659
673
|
});
|
|
660
674
|
return { ...cliResult, reuseRuntimeActive };
|
|
661
675
|
}
|
|
662
|
-
async function executeSingleCliAttempt(options, modelConfig) {
|
|
676
|
+
async function executeSingleCliAttempt(options, modelConfig, promptFilePath) {
|
|
663
677
|
const modelDisplay = modelConfig.provider && modelConfig.model
|
|
664
678
|
? `${modelConfig.provider}/${modelConfig.model}`
|
|
665
679
|
: modelConfig.model ?? 'default';
|
|
@@ -687,13 +701,17 @@ async function executeSingleCliAttempt(options, modelConfig) {
|
|
|
687
701
|
piArgs.push('--thinking', modelConfig.thinking);
|
|
688
702
|
}
|
|
689
703
|
piArgs.push('--tools', options.toolNames.join(','));
|
|
690
|
-
piArgs.push('--append-system-prompt',
|
|
704
|
+
piArgs.push('--append-system-prompt', promptFilePath);
|
|
691
705
|
piArgs.push(...options.attachedFiles.map((file) => `@${file}`));
|
|
692
706
|
piArgs.push(options.userMessage);
|
|
707
|
+
const recordedPiArgs = piArgs.map((arg, index) => index > 0 && piArgs[index - 1] === '--append-system-prompt'
|
|
708
|
+
? '<system-prompt-file>'
|
|
709
|
+
: arg);
|
|
693
710
|
const startedAt = Date.now();
|
|
694
711
|
return await new Promise((resolve, reject) => {
|
|
695
712
|
const child = spawn(process.execPath, [getPiCliPath(), ...piArgs], {
|
|
696
713
|
cwd: options.repoRoot,
|
|
714
|
+
...processTreeSpawnOptions(),
|
|
697
715
|
env: {
|
|
698
716
|
...process.env,
|
|
699
717
|
// Explicitly propagate API keys that pi needs to recognise providers.
|
|
@@ -748,7 +766,7 @@ async function executeSingleCliAttempt(options, modelConfig) {
|
|
|
748
766
|
});
|
|
749
767
|
resolve({
|
|
750
768
|
assistantText: collected.assistantText,
|
|
751
|
-
command: ['pi', ...
|
|
769
|
+
command: ['pi', ...recordedPiArgs],
|
|
752
770
|
durationMs,
|
|
753
771
|
exitCode: code,
|
|
754
772
|
failureCategory,
|
|
@@ -771,7 +789,7 @@ async function executeSingleCliAttempt(options, modelConfig) {
|
|
|
771
789
|
return;
|
|
772
790
|
timedOut = true;
|
|
773
791
|
supervisionStderr = reason;
|
|
774
|
-
child
|
|
792
|
+
terminateProcessTree(child, 'SIGTERM');
|
|
775
793
|
sigkillHandle = setTimeout(() => {
|
|
776
794
|
if (settled)
|
|
777
795
|
return;
|
|
@@ -779,7 +797,7 @@ async function executeSingleCliAttempt(options, modelConfig) {
|
|
|
779
797
|
finish(child.exitCode);
|
|
780
798
|
return;
|
|
781
799
|
}
|
|
782
|
-
child
|
|
800
|
+
terminateProcessTree(child, 'SIGKILL');
|
|
783
801
|
exitConfirmationHandle = setTimeout(() => {
|
|
784
802
|
if (settled)
|
|
785
803
|
return;
|