@tea-agent/loop-agent 0.24.7 → 0.24.8
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
CHANGED
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
|
|
11
11
|
- 修复前端证据校验在 Windows 下通过长 `node -e` 命令执行时的终端转义崩溃:改为由 runtime 内部直接校验,兼容 CMD、Git Bash、PowerShell 与 Unix shell
|
|
12
12
|
|
|
13
|
+
## [0.24.8] - 2026-07-29
|
|
14
|
+
|
|
15
|
+
### 重点更新
|
|
16
|
+
|
|
17
|
+
- 修复 FINAL-VERIFY 改写已存在的 allowed_paths 证据(gitignore)时 Git checkpoint 误失败,以及 record-error 后 Task Pool 卡在 Running 无法 mark-failed/retry 的问题
|
|
18
|
+
|
|
19
|
+
### 修复
|
|
20
|
+
|
|
21
|
+
- `finalizeGitTask` 对 allowed_paths 内的 pre-existing ignored evidence 允许 rewrite 并 force-add
|
|
22
|
+
- `runReadyTasks` 在 record-error 时 best-effort 投影 `runs.jsonl` + Failed 状态,便于官方 retry
|
|
23
|
+
|
|
13
24
|
## [0.24.7] - 2026-07-29
|
|
14
25
|
|
|
15
26
|
### 重点更新
|
|
@@ -101,11 +101,12 @@ export async function finalizeGitTask(transaction, outcome) {
|
|
|
101
101
|
await assertTransactionPosition(transaction.repoRoot, current);
|
|
102
102
|
const currentIgnored = await readIgnoredBaseline(transaction.repoRoot);
|
|
103
103
|
const ignoredBaselineByPath = new Map(current.ignoredBaseline.map((entry) => [entry.path, entry.sha256]));
|
|
104
|
-
const changedBaselineIgnored = currentIgnored
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
const changedBaselineIgnored = currentIgnored
|
|
105
|
+
.filter((entry) => ignoredBaselineByPath.has(entry.path) && ignoredBaselineByPath.get(entry.path) !== entry.sha256)
|
|
106
|
+
.map((entry) => entry.path);
|
|
107
|
+
const missingBaselineIgnored = current.ignoredBaseline
|
|
108
|
+
.filter((entry) => !currentIgnored.some((candidate) => candidate.path === entry.path))
|
|
109
|
+
.map((entry) => entry.path);
|
|
109
110
|
const existing = current.checkpoints.find((entry) => entry.workerRunId === outcome.workerRunId);
|
|
110
111
|
if (outcome.status === "reused" || existing) {
|
|
111
112
|
if (!existing)
|
|
@@ -123,12 +124,28 @@ export async function finalizeGitTask(transaction, outcome) {
|
|
|
123
124
|
const newIgnored = currentIgnored.map((entry) => entry.path).filter((entry) => !ignoredBaselineByPath.has(entry));
|
|
124
125
|
if (outcome.status === "succeeded") {
|
|
125
126
|
const allowedPaths = expandAllowedPathsForWorkflow(resolveWorkflow(outcome.taskSpec).workflow, outcome.taskSpec.constraints.allowed_paths);
|
|
127
|
+
const isAllowedEvidencePath = (entry) => !isSensitivePath(entry) &&
|
|
128
|
+
!isEphemeralToolCachePath(entry) &&
|
|
129
|
+
allowedPaths.some((glob) => matchesGlob(entry, glob));
|
|
130
|
+
// Pre-existing ignored evidence under allowed_paths may be rewritten by
|
|
131
|
+
// FINAL-VERIFY smoke / verify-final and must force-add into the checkpoint.
|
|
132
|
+
// Other baseline ignored mutations remain fail-closed (e.g. dist/, secrets).
|
|
133
|
+
const blockedBaselineChanged = changedBaselineIgnored.filter((entry) => !isAllowedEvidencePath(entry));
|
|
134
|
+
const blockedBaselineMissing = missingBaselineIgnored.filter((entry) => !isEphemeralToolCachePath(entry) && !isAllowedEvidencePath(entry));
|
|
135
|
+
if (blockedBaselineChanged.length > 0 || blockedBaselineMissing.length > 0) {
|
|
136
|
+
throw new Error(`pre-existing ignored file changed during Git transaction: ${[...blockedBaselineChanged, ...blockedBaselineMissing].join(", ")}`);
|
|
137
|
+
}
|
|
126
138
|
// Evidence under allowed_paths may be gitignored for local convenience (e.g.
|
|
127
139
|
// reports/welcome/** smoke JSON) but must still checkpoint for Task Pool Done.
|
|
128
140
|
// Sensitive / out-of-scope ignored files remain fail-closed; ephemeral tool
|
|
129
141
|
// caches neither block nor force-add.
|
|
130
142
|
const forceAddIgnored = [];
|
|
131
143
|
const blockedIgnored = [];
|
|
144
|
+
const rewrittenBaselineEvidence = changedBaselineIgnored.filter((entry) => isAllowedEvidencePath(entry));
|
|
145
|
+
for (const entry of rewrittenBaselineEvidence) {
|
|
146
|
+
if (!forceAddIgnored.includes(entry))
|
|
147
|
+
forceAddIgnored.push(entry);
|
|
148
|
+
}
|
|
132
149
|
for (const entry of newIgnored) {
|
|
133
150
|
if (isEphemeralToolCachePath(entry))
|
|
134
151
|
continue;
|
|
@@ -137,7 +154,8 @@ export async function finalizeGitTask(transaction, outcome) {
|
|
|
137
154
|
continue;
|
|
138
155
|
}
|
|
139
156
|
if (allowedPaths.some((glob) => matchesGlob(entry, glob))) {
|
|
140
|
-
forceAddIgnored.
|
|
157
|
+
if (!forceAddIgnored.includes(entry))
|
|
158
|
+
forceAddIgnored.push(entry);
|
|
141
159
|
}
|
|
142
160
|
else {
|
|
143
161
|
blockedIgnored.push(entry);
|
|
@@ -166,7 +184,10 @@ export async function finalizeGitTask(transaction, outcome) {
|
|
|
166
184
|
await assertTransactionPosition(transaction.repoRoot, { ...current, lastCheckpoint: commit });
|
|
167
185
|
await assertCheckpointMetadata(transaction.repoRoot, commit, outcome.taskSpec, outcome.workerRunId);
|
|
168
186
|
const afterCommitIgnored = await readIgnoredBaseline(transaction.repoRoot);
|
|
169
|
-
|
|
187
|
+
// force-add may promote allowed evidence from ignored → tracked; drop those
|
|
188
|
+
// paths from the post-commit baseline equality check.
|
|
189
|
+
const forceAdded = new Set(forceAddIgnored);
|
|
190
|
+
assertIgnoredBaselineUnchanged(current.ignoredBaseline.filter((entry) => !isEphemeralToolCachePath(entry.path) && !forceAdded.has(entry.path)), afterCommitIgnored.filter((entry) => !isEphemeralToolCachePath(entry.path) && !forceAdded.has(entry.path)));
|
|
170
191
|
}
|
|
171
192
|
catch (error) {
|
|
172
193
|
const branch = await git(transaction.repoRoot, ["branch", "--show-current"]).catch(() => "");
|
|
@@ -199,6 +220,10 @@ export async function finalizeGitTask(transaction, outcome) {
|
|
|
199
220
|
changedFiles: checkpointFiles,
|
|
200
221
|
};
|
|
201
222
|
}
|
|
223
|
+
// Failed / keep-diff paths still fail closed on unexpected baseline ignored mutations.
|
|
224
|
+
if (changedBaselineIgnored.length > 0 || missingBaselineIgnored.length > 0) {
|
|
225
|
+
throw new Error(`pre-existing ignored file changed during Git transaction: ${[...changedBaselineIgnored, ...missingBaselineIgnored].join(", ")}`);
|
|
226
|
+
}
|
|
202
227
|
const artifactDir = path.join(path.dirname(transaction.recordPath), "failures", outcome.workerRunId);
|
|
203
228
|
await captureFailureArtifacts(transaction.repoRoot, artifactDir, changes, newIgnored, outcome);
|
|
204
229
|
if (outcome.keepFailedDiff) {
|
|
@@ -304,11 +304,51 @@ export async function runReadyTasks(options) {
|
|
|
304
304
|
if (result.status !== "succeeded") {
|
|
305
305
|
await options.onTaskFinalized?.({ status: result.status, taskSpec, workerRunId: result.workerRunId, runRecordPath: result.runRecordPath });
|
|
306
306
|
}
|
|
307
|
+
const recordErrorMessage = errorMessage(error);
|
|
308
|
+
// Always project a Pool run fact for record-error so operators can
|
|
309
|
+
// mark-failed / retry without hand-editing Running state.
|
|
310
|
+
try {
|
|
311
|
+
await recordTaskPoolRun({
|
|
312
|
+
repoRoot: options.repoRoot,
|
|
313
|
+
run: {
|
|
314
|
+
schemaVersion: 1,
|
|
315
|
+
batchRunId,
|
|
316
|
+
workerRunId: result.workerRunId,
|
|
317
|
+
taskId: result.businessId,
|
|
318
|
+
featureId: taskSpec.feature_id,
|
|
319
|
+
status: "run-error",
|
|
320
|
+
recordedAt: new Date().toISOString(),
|
|
321
|
+
error: recordErrorMessage,
|
|
322
|
+
runRecordPath: result.runRecordPath,
|
|
323
|
+
...(result.workflow ? { workflow: result.workflow } : {}),
|
|
324
|
+
...(controllerIdentity ? { controllerIdentity } : {}),
|
|
325
|
+
...(retryOfWorkerRunId ? { retryOfWorkerRunId } : {}),
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
await writeTaskPoolState(options.repoRoot, {
|
|
329
|
+
schemaVersion: 2,
|
|
330
|
+
featureId: taskSpec.feature_id,
|
|
331
|
+
taskId,
|
|
332
|
+
status: "Failed",
|
|
333
|
+
updatedAt: new Date().toISOString(),
|
|
334
|
+
workerRunId: result.workerRunId,
|
|
335
|
+
lastRunRecordPath: result.runRecordPath,
|
|
336
|
+
failure: {
|
|
337
|
+
category: "EnvFailure",
|
|
338
|
+
recommendedFollowUpKind: "manual-review",
|
|
339
|
+
derivedFollowUpTaskId: `${taskId}-record-error-review`,
|
|
340
|
+
source: "report-decision",
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
// Best-effort; batch still reports record-error.
|
|
346
|
+
}
|
|
307
347
|
tasks.push({
|
|
308
348
|
taskId,
|
|
309
349
|
workerRunId: result.workerRunId,
|
|
310
350
|
status: "record-error",
|
|
311
|
-
error:
|
|
351
|
+
error: recordErrorMessage,
|
|
312
352
|
runRecordPath: result.runRecordPath,
|
|
313
353
|
});
|
|
314
354
|
break;
|