@lazyingart/agintiflow 0.20.252 → 0.20.253
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.253",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -341,6 +341,129 @@ assert.ok(
|
|
|
341
341
|
"DeepSeek runtime compaction exceeded the bounded retry target"
|
|
342
342
|
);
|
|
343
343
|
|
|
344
|
+
const staleReadAfterMutationState = {
|
|
345
|
+
goal: "Repair service_ctl.py from current source and verify the service lifecycle tests.",
|
|
346
|
+
plan: "Use current source state and latest test evidence.",
|
|
347
|
+
messages: [
|
|
348
|
+
{ role: "system", content: "Preserve current source truth across compaction." },
|
|
349
|
+
{ role: "user", content: "Continue the current service recovery task." },
|
|
350
|
+
{
|
|
351
|
+
role: "assistant",
|
|
352
|
+
content: "",
|
|
353
|
+
tool_calls: [
|
|
354
|
+
{
|
|
355
|
+
id: "stale-source-read",
|
|
356
|
+
type: "function",
|
|
357
|
+
function: {
|
|
358
|
+
name: "read_file",
|
|
359
|
+
arguments: JSON.stringify({ path: "service_ctl.py", startLine: 1, lineLimit: 40 }),
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
role: "tool",
|
|
366
|
+
tool_call_id: "stale-source-read",
|
|
367
|
+
content: JSON.stringify({
|
|
368
|
+
ok: true,
|
|
369
|
+
toolName: "read_file",
|
|
370
|
+
path: "service_ctl.py",
|
|
371
|
+
startLine: 1,
|
|
372
|
+
lineLimit: 40,
|
|
373
|
+
lineCount: 80,
|
|
374
|
+
bytes: 3200,
|
|
375
|
+
sha256: "1111111111111111111111111111111111111111111111111111111111111111",
|
|
376
|
+
contentTruncated: false,
|
|
377
|
+
content: "STALE-SERVICE-CONTENT command = f'python gateway_service.py'",
|
|
378
|
+
}),
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
role: "assistant",
|
|
382
|
+
content: "",
|
|
383
|
+
tool_calls: [
|
|
384
|
+
{
|
|
385
|
+
id: "current-source-mutation",
|
|
386
|
+
type: "function",
|
|
387
|
+
function: {
|
|
388
|
+
name: "apply_patch",
|
|
389
|
+
arguments: JSON.stringify({
|
|
390
|
+
path: "service_ctl.py",
|
|
391
|
+
search: "command = f'python gateway_service.py'",
|
|
392
|
+
replace: "command = [sys.executable, 'gateway_service.py']",
|
|
393
|
+
}),
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
role: "tool",
|
|
400
|
+
tool_call_id: "current-source-mutation",
|
|
401
|
+
content: JSON.stringify({
|
|
402
|
+
ok: true,
|
|
403
|
+
toolName: "apply_patch",
|
|
404
|
+
path: "service_ctl.py",
|
|
405
|
+
summary: "1 file change applied",
|
|
406
|
+
}),
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
role: "assistant",
|
|
410
|
+
content: "",
|
|
411
|
+
tool_calls: [
|
|
412
|
+
{
|
|
413
|
+
id: "fresh-source-read",
|
|
414
|
+
type: "function",
|
|
415
|
+
function: {
|
|
416
|
+
name: "read_file",
|
|
417
|
+
arguments: JSON.stringify({ path: "service_ctl.py", startLine: 41, lineLimit: 40 }),
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
role: "tool",
|
|
424
|
+
tool_call_id: "fresh-source-read",
|
|
425
|
+
content: JSON.stringify({
|
|
426
|
+
ok: true,
|
|
427
|
+
toolName: "read_file",
|
|
428
|
+
path: "service_ctl.py",
|
|
429
|
+
startLine: 41,
|
|
430
|
+
lineLimit: 40,
|
|
431
|
+
lineCount: 80,
|
|
432
|
+
bytes: 3300,
|
|
433
|
+
sha256: "2222222222222222222222222222222222222222222222222222222222222222",
|
|
434
|
+
contentTruncated: false,
|
|
435
|
+
content: "FRESH-SERVICE-CONTENT shell=False and lifecycle helpers are current",
|
|
436
|
+
}),
|
|
437
|
+
},
|
|
438
|
+
...Array.from({ length: 8 }, (_, index) => readOnlyDiagnosticPair(index + 30)).flat(),
|
|
439
|
+
],
|
|
440
|
+
};
|
|
441
|
+
const staleReadAfterMutationMessages = buildContextBudgetCompactionMessages(
|
|
442
|
+
staleReadAfterMutationState,
|
|
443
|
+
config,
|
|
444
|
+
{ title: "", url: "" },
|
|
445
|
+
12,
|
|
446
|
+
{ reason: "discard stale pre-mutation source reads" }
|
|
447
|
+
);
|
|
448
|
+
const staleReadAfterMutationText = staleReadAfterMutationMessages
|
|
449
|
+
.map((message) => message.content || "")
|
|
450
|
+
.join("\n");
|
|
451
|
+
assert.ok(
|
|
452
|
+
!staleReadAfterMutationText.includes("STALE-SERVICE-CONTENT"),
|
|
453
|
+
"compaction retained a source read that predates a successful mutation of the same file"
|
|
454
|
+
);
|
|
455
|
+
assert.ok(
|
|
456
|
+
staleReadAfterMutationText.includes("FRESH-SERVICE-CONTENT"),
|
|
457
|
+
"compaction discarded the bounded source read made after the successful mutation"
|
|
458
|
+
);
|
|
459
|
+
assert.ok(
|
|
460
|
+
staleReadAfterMutationMessages.some((message) =>
|
|
461
|
+
Array.isArray(message.tool_calls) &&
|
|
462
|
+
message.tool_calls.some((call) => call?.function?.name === "apply_patch")
|
|
463
|
+
),
|
|
464
|
+
"compaction discarded the successful mutation while invalidating its stale predecessor read"
|
|
465
|
+
);
|
|
466
|
+
|
|
344
467
|
function noisyFullReadPair(index, generation) {
|
|
345
468
|
const id = `validator-${generation}-${index}`;
|
|
346
469
|
const file = `tmp/validator-${generation}-${index}.py`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
+
import crypto from "node:crypto";
|
|
3
4
|
import fs from "node:fs/promises";
|
|
4
5
|
import os from "node:os";
|
|
5
6
|
import path from "node:path";
|
|
@@ -11,7 +12,11 @@ import {
|
|
|
11
12
|
decideLocalFailureRecovery,
|
|
12
13
|
localFailureRecoveryInstruction,
|
|
13
14
|
} from "../src/local-failure-recovery.js";
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
nextStepRuntimeConfig,
|
|
17
|
+
repeatedSuccessfulMutationBlock,
|
|
18
|
+
runAgent,
|
|
19
|
+
} from "../src/agent-runner.js";
|
|
15
20
|
import { resolveRuntimeConfig } from "../src/config.js";
|
|
16
21
|
import { SessionStore } from "../src/session-store.js";
|
|
17
22
|
|
|
@@ -190,6 +195,89 @@ assert.match(instruction, /preserve successful work/i);
|
|
|
190
195
|
assert.match(instruction, /Do not repeat the failing call/i);
|
|
191
196
|
assert.match(instruction, /rerun the smallest relevant verification/i);
|
|
192
197
|
|
|
198
|
+
const durablePatchArgs = {
|
|
199
|
+
path: "service_ctl.py",
|
|
200
|
+
search: "command = f'python gateway_service.py'",
|
|
201
|
+
replace: "command = [sys.executable, 'gateway_service.py']",
|
|
202
|
+
};
|
|
203
|
+
const digest = (value) => crypto.createHash("sha256").update(value).digest("hex");
|
|
204
|
+
const durableMutation = {
|
|
205
|
+
revision: 4,
|
|
206
|
+
goalRevision: 7,
|
|
207
|
+
toolName: "apply_patch",
|
|
208
|
+
paths: ["service_ctl.py"],
|
|
209
|
+
patch: {
|
|
210
|
+
path: "./service_ctl.py",
|
|
211
|
+
searchHash: digest(durablePatchArgs.search),
|
|
212
|
+
replaceHash: digest(durablePatchArgs.replace),
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
const durablePatchState = {
|
|
216
|
+
meta: {
|
|
217
|
+
goalContract: { revision: 7 },
|
|
218
|
+
toolLoop: {
|
|
219
|
+
recent: Array.from({ length: 20 }, (_, index) =>
|
|
220
|
+
failed("apply_patch", `later-failure-${index}`)
|
|
221
|
+
),
|
|
222
|
+
stagnationEpoch: 23,
|
|
223
|
+
},
|
|
224
|
+
projectVerification: {
|
|
225
|
+
mutationRevision: 4,
|
|
226
|
+
mutationHistory: [durableMutation],
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
assert.equal(
|
|
231
|
+
repeatedSuccessfulMutationBlock(durablePatchState, "apply_patch", durablePatchArgs, {
|
|
232
|
+
commandCwd: process.cwd(),
|
|
233
|
+
})?.category,
|
|
234
|
+
"repeated-successful-mutation",
|
|
235
|
+
"a successful exact patch must remain blocked after it falls out of the short tool-loop window"
|
|
236
|
+
);
|
|
237
|
+
assert.equal(
|
|
238
|
+
repeatedSuccessfulMutationBlock(
|
|
239
|
+
{
|
|
240
|
+
...durablePatchState,
|
|
241
|
+
meta: {
|
|
242
|
+
...durablePatchState.meta,
|
|
243
|
+
goalContract: { revision: 8 },
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
"apply_patch",
|
|
247
|
+
durablePatchArgs,
|
|
248
|
+
{ commandCwd: process.cwd() }
|
|
249
|
+
),
|
|
250
|
+
null,
|
|
251
|
+
"a genuine user continuation must permit deliberate reconsideration of an earlier patch"
|
|
252
|
+
);
|
|
253
|
+
assert.equal(
|
|
254
|
+
repeatedSuccessfulMutationBlock(
|
|
255
|
+
{
|
|
256
|
+
...durablePatchState,
|
|
257
|
+
meta: {
|
|
258
|
+
...durablePatchState.meta,
|
|
259
|
+
projectVerification: {
|
|
260
|
+
mutationRevision: 5,
|
|
261
|
+
mutationHistory: [
|
|
262
|
+
durableMutation,
|
|
263
|
+
{
|
|
264
|
+
revision: 5,
|
|
265
|
+
goalRevision: 7,
|
|
266
|
+
toolName: "write_file",
|
|
267
|
+
paths: ["service_ctl.py"],
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
"apply_patch",
|
|
274
|
+
durablePatchArgs,
|
|
275
|
+
{ commandCwd: process.cwd() }
|
|
276
|
+
),
|
|
277
|
+
null,
|
|
278
|
+
"an intervening successful mutation must permit the same exact patch when source state changed"
|
|
279
|
+
);
|
|
280
|
+
|
|
193
281
|
function assistant(content, toolCalls = []) {
|
|
194
282
|
return {
|
|
195
283
|
choices: [{ message: { role: "assistant", content, ...(toolCalls.length ? { tool_calls: toolCalls } : {}) } }],
|
package/src/agent-runner.js
CHANGED
|
@@ -877,11 +877,22 @@ function summarizeRetainedSourceEvidence(messages = [], limit = 28) {
|
|
|
877
877
|
const payload = retained?.payload || safeParseToolContent(message.content);
|
|
878
878
|
if (!payload || payload.ok === false || payload.blocked || payload.skipped) continue;
|
|
879
879
|
const toolName = String(retained?.name || payload.toolName || payload.name || "");
|
|
880
|
+
const args = retained?.args || payload.args || {};
|
|
881
|
+
const sourcePath = String(payload.path || args.path || "").trim();
|
|
882
|
+
if (["apply_patch", "write_file"].includes(toolName) && sourcePath) {
|
|
883
|
+
for (const [key, record] of bySource.entries()) {
|
|
884
|
+
if (
|
|
885
|
+
record.toolName === "read_file" &&
|
|
886
|
+
retainedPathMatchesAny(record.sourcePath, [sourcePath])
|
|
887
|
+
) {
|
|
888
|
+
bySource.delete(key);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
880
893
|
if (!["read_file", "list_files", "search_files", "inspect_project", "run_command"].includes(toolName)) {
|
|
881
894
|
continue;
|
|
882
895
|
}
|
|
883
|
-
const args = retained?.args || payload.args || {};
|
|
884
|
-
const sourcePath = String(payload.path || args.path || "").trim();
|
|
885
896
|
const command = String(args.command || "").trim();
|
|
886
897
|
const readRange = toolName === "read_file" ? retainedReadRange(payload, args) : null;
|
|
887
898
|
const key = `${toolName}:${sourcePath || command}${readRange ? `:${readRange.key}` : ""}`;
|
|
@@ -937,9 +948,15 @@ function summarizeRetainedSourceEvidence(messages = [], limit = 28) {
|
|
|
937
948
|
if (payload.stdout) parts.push(`stdout=${compactSingleLine(payload.stdout, 260)}`);
|
|
938
949
|
if (payload.stderr) parts.push(`stderr=${compactSingleLine(payload.stderr, 220)}`);
|
|
939
950
|
}
|
|
940
|
-
bySource.set(key,
|
|
951
|
+
bySource.set(key, {
|
|
952
|
+
toolName,
|
|
953
|
+
sourcePath,
|
|
954
|
+
text: redactSensitiveText(parts.join(" | ")),
|
|
955
|
+
});
|
|
941
956
|
}
|
|
942
|
-
return [...bySource.values()]
|
|
957
|
+
return [...bySource.values()]
|
|
958
|
+
.slice(-Math.max(1, Number(limit) || 28))
|
|
959
|
+
.map((record) => record.text);
|
|
943
960
|
}
|
|
944
961
|
|
|
945
962
|
const COMPACTION_STATE_TOOL_NAMES = new Set([
|
|
@@ -1076,6 +1093,16 @@ function retainedToolStateMessages(messages = [], limit = 12, outputPaths = [],
|
|
|
1076
1093
|
if (!COMPACTION_STATE_TOOL_NAMES.has(name)) return;
|
|
1077
1094
|
if (!payload || payload.ok === false || payload.blocked || payload.skipped) return;
|
|
1078
1095
|
const sourcePath = String(payload.path || args?.path || "").trim();
|
|
1096
|
+
if (["apply_patch", "write_file"].includes(name) && sourcePath) {
|
|
1097
|
+
for (const [key, record] of recordsByKey.entries()) {
|
|
1098
|
+
if (
|
|
1099
|
+
record.name === "read_file" &&
|
|
1100
|
+
retainedPathMatchesAny(record.sourcePath, [sourcePath])
|
|
1101
|
+
) {
|
|
1102
|
+
recordsByKey.delete(key);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1079
1106
|
const command = String(args?.command || payload.args?.command || "").trim();
|
|
1080
1107
|
const durableIdentity = String(
|
|
1081
1108
|
sourcePath || command || payload.researchId || args?.researchId || args?.query || name
|
|
@@ -1085,6 +1112,7 @@ function retainedToolStateMessages(messages = [], limit = 12, outputPaths = [],
|
|
|
1085
1112
|
recordsByKey.set(key, {
|
|
1086
1113
|
ordinal: ordinal += 1,
|
|
1087
1114
|
name,
|
|
1115
|
+
sourcePath,
|
|
1088
1116
|
args: redactValue(args),
|
|
1089
1117
|
payload: compactRetainedToolPayload(name, payload, args),
|
|
1090
1118
|
});
|
|
@@ -6200,7 +6228,7 @@ export function repeatedSuccessfulMutationBlock(state, toolName, args = {}, conf
|
|
|
6200
6228
|
commandCwd: config.commandCwd,
|
|
6201
6229
|
});
|
|
6202
6230
|
const stagnationEpoch = Math.max(0, Number(toolLoop.stagnationEpoch || 0));
|
|
6203
|
-
const
|
|
6231
|
+
const recentlyApplied = (Array.isArray(toolLoop.recent) ? toolLoop.recent : []).some(
|
|
6204
6232
|
(entry) =>
|
|
6205
6233
|
entry?.signature === signature &&
|
|
6206
6234
|
entry?.toolName === "apply_patch" &&
|
|
@@ -6209,6 +6237,41 @@ export function repeatedSuccessfulMutationBlock(state, toolName, args = {}, conf
|
|
|
6209
6237
|
entry?.successfulMutation === true &&
|
|
6210
6238
|
Number(entry?.stagnationEpoch || 0) === stagnationEpoch
|
|
6211
6239
|
);
|
|
6240
|
+
const verification = state.meta?.projectVerification || {};
|
|
6241
|
+
const history = Array.isArray(verification.mutationHistory)
|
|
6242
|
+
? verification.mutationHistory
|
|
6243
|
+
: verification.lastMutation
|
|
6244
|
+
? [verification.lastMutation]
|
|
6245
|
+
: [];
|
|
6246
|
+
const targetPath = typeof args.path === "string" ? safeRecoveryEvidencePath(args.path) : "";
|
|
6247
|
+
const searchHash = typeof args.search === "string" ? hashForLog(args.search) : "";
|
|
6248
|
+
const replaceHash = typeof args.replace === "string" ? hashForLog(args.replace) : "";
|
|
6249
|
+
const currentGoalRevision = Math.max(0, Number(state.meta?.goalContract?.revision || 0));
|
|
6250
|
+
let matchingHistoryIndex = -1;
|
|
6251
|
+
if (targetPath && searchHash && replaceHash) {
|
|
6252
|
+
for (let index = history.length - 1; index >= 0; index -= 1) {
|
|
6253
|
+
const mutation = history[index];
|
|
6254
|
+
if (
|
|
6255
|
+
mutation?.toolName === "apply_patch" &&
|
|
6256
|
+
Number(mutation?.goalRevision || 0) === currentGoalRevision &&
|
|
6257
|
+
safeRecoveryEvidencePath(mutation?.patch?.path) === targetPath &&
|
|
6258
|
+
String(mutation?.patch?.searchHash || "") === searchHash &&
|
|
6259
|
+
String(mutation?.patch?.replaceHash || "") === replaceHash
|
|
6260
|
+
) {
|
|
6261
|
+
matchingHistoryIndex = index;
|
|
6262
|
+
break;
|
|
6263
|
+
}
|
|
6264
|
+
}
|
|
6265
|
+
}
|
|
6266
|
+
const persistentlyApplied =
|
|
6267
|
+
matchingHistoryIndex >= 0 &&
|
|
6268
|
+
!history.slice(matchingHistoryIndex + 1).some(
|
|
6269
|
+
(mutation) =>
|
|
6270
|
+
Number(mutation?.goalRevision || 0) === currentGoalRevision &&
|
|
6271
|
+
Number(mutation?.revision || 0) >
|
|
6272
|
+
Number(history[matchingHistoryIndex]?.revision || 0)
|
|
6273
|
+
);
|
|
6274
|
+
const alreadyApplied = recentlyApplied || persistentlyApplied;
|
|
6212
6275
|
if (!alreadyApplied) return null;
|
|
6213
6276
|
return {
|
|
6214
6277
|
reason:
|