@lazyingart/agintiflow 0.20.245 → 0.20.247

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.245",
3
+ "version": "0.20.247",
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",
@@ -51,6 +51,25 @@ const artifactContract = deriveScsTaskContract({
51
51
  taskProfile: "chatops",
52
52
  });
53
53
  assert.equal(artifactContract.requiresExternalEvidence, true, "real chat artifact work lost its evidence gate");
54
+ const retainedReportContract = deriveScsTaskContract({
55
+ goal: [
56
+ "The completed evidence is already saved in tmp/reliability-evidence-pass.md and must remain read-only.",
57
+ "Read only missing bounded ranges of tmp/reliability-evidence-pass.md.",
58
+ "Rewrite agent-reliability-evidence-review.md as a concise decision document.",
59
+ "Rebuild sources.json so it contains only cited sources.",
60
+ ].join("\n"),
61
+ taskProfile: "research",
62
+ });
63
+ assert.deepEqual(
64
+ retainedReportContract.exactOutputPaths,
65
+ ["agent-reliability-evidence-review.md", "sources.json"],
66
+ "report continuation confused an existing saved input with rewrite/rebuild outputs"
67
+ );
68
+ assert.deepEqual(
69
+ retainedReportContract.exactInputPaths,
70
+ ["tmp/reliability-evidence-pass.md"],
71
+ "report continuation lost the read-only evidence input"
72
+ );
54
73
  assert.ok(artifactContract.requiredEvidence.some((item) => item.category === "artifact"));
55
74
  const scopedArtifactRootContract = deriveScsTaskContract({
56
75
  goal:
@@ -322,4 +341,135 @@ assert.ok(
322
341
  "DeepSeek runtime compaction exceeded the bounded retry target"
323
342
  );
324
343
 
344
+ function noisyFullReadPair(index, generation) {
345
+ const id = `validator-${generation}-${index}`;
346
+ const file = `tmp/validator-${generation}-${index}.py`;
347
+ return [
348
+ {
349
+ role: "assistant",
350
+ content: "",
351
+ reasoning_content: "Inspect the latest validator.",
352
+ tool_calls: [
353
+ {
354
+ id,
355
+ type: "function",
356
+ function: { name: "read_file", arguments: JSON.stringify({ path: file }) },
357
+ },
358
+ ],
359
+ },
360
+ {
361
+ role: "tool",
362
+ tool_call_id: id,
363
+ content: JSON.stringify({
364
+ ok: true,
365
+ toolName: "read_file",
366
+ path: file,
367
+ bytes: 4096,
368
+ lineCount: 120,
369
+ sha256: `${generation}${String(index).padStart(2, "0")}`.repeat(24).slice(0, 64),
370
+ contentTruncated: false,
371
+ content: `VALIDATOR-${generation}-${index}\n${"broad validator content ".repeat(150)}`,
372
+ }),
373
+ },
374
+ ];
375
+ }
376
+
377
+ function boundedOutputReadPair(index, generation) {
378
+ const id = `output-${generation}-${index}`;
379
+ return [
380
+ {
381
+ role: "assistant",
382
+ content: "",
383
+ reasoning_content: "Inspect the existing mutable output.",
384
+ tool_calls: [
385
+ {
386
+ id,
387
+ type: "function",
388
+ function: {
389
+ name: "read_file",
390
+ arguments: JSON.stringify({
391
+ path: "agent-reliability-evidence-review.md",
392
+ startLine: 1 + (index - 1) * 40,
393
+ lineLimit: 40,
394
+ }),
395
+ },
396
+ },
397
+ ],
398
+ },
399
+ {
400
+ role: "tool",
401
+ tool_call_id: id,
402
+ content: JSON.stringify({
403
+ ok: true,
404
+ toolName: "read_file",
405
+ path: "agent-reliability-evidence-review.md",
406
+ startLine: 1 + (index - 1) * 40,
407
+ lineLimit: 40,
408
+ lineCount: 240,
409
+ bytes: 24000,
410
+ sha256: `${generation}${String(index).padStart(2, "0")}`.repeat(24).slice(0, 64),
411
+ contentTruncated: false,
412
+ content: `MUTABLE-OUTPUT-${generation}-${index}\n${"old output content ".repeat(170)}`,
413
+ }),
414
+ },
415
+ ];
416
+ }
417
+
418
+ const twiceCompactedState = {
419
+ ...compactionState,
420
+ meta: {
421
+ scs: {
422
+ taskContract: {
423
+ exactOutputPaths: ["agent-reliability-evidence-review.md", "sources.json"],
424
+ },
425
+ },
426
+ },
427
+ messages: [
428
+ ...deepSeekRuntimeMessages,
429
+ ...Array.from({ length: 14 }, (_, index) => noisyFullReadPair(index + 1, 2)).flat(),
430
+ ...Array.from({ length: 6 }, (_, index) => boundedOutputReadPair(index + 1, 2)).flat(),
431
+ ],
432
+ };
433
+ const twiceCompacted = buildContextBudgetCompactionMessages(
434
+ twiceCompactedState,
435
+ { ...config, provider: "deepseek", model: "deepseek-chat" },
436
+ { title: "", url: "" },
437
+ 8,
438
+ { reason: "second DeepSeek compaction" }
439
+ );
440
+ const twiceCompactedText = twiceCompacted.map((message) => message.content || "").join("\n");
441
+ assert.ok(twiceCompactedText.includes("reliability-evidence-v14"));
442
+ assert.ok(twiceCompactedText.includes("EVIDENCE-CHUNK-ONE"));
443
+ assert.ok(twiceCompactedText.includes("EVIDENCE-CHUNK-TWO"));
444
+ assert.equal(
445
+ (twiceCompactedText.match(/Tool: deep_research/g) || []).length,
446
+ 1,
447
+ "second DeepSeek compaction duplicated the completed research record"
448
+ );
449
+
450
+ const thriceCompactedState = {
451
+ ...compactionState,
452
+ meta: twiceCompactedState.meta,
453
+ messages: [
454
+ ...twiceCompacted,
455
+ ...Array.from({ length: 14 }, (_, index) => noisyFullReadPair(index + 1, 3)).flat(),
456
+ ...Array.from({ length: 6 }, (_, index) => boundedOutputReadPair(index + 1, 3)).flat(),
457
+ ],
458
+ };
459
+ const thriceCompacted = buildContextBudgetCompactionMessages(
460
+ thriceCompactedState,
461
+ { ...config, provider: "deepseek", model: "deepseek-chat" },
462
+ { title: "", url: "" },
463
+ 12,
464
+ { reason: "third DeepSeek compaction" }
465
+ );
466
+ const thriceCompactedText = thriceCompacted.map((message) => message.content || "").join("\n");
467
+ assert.ok(thriceCompactedText.includes("reliability-evidence-v14"));
468
+ assert.ok(thriceCompactedText.includes("EVIDENCE-CHUNK-ONE"));
469
+ assert.ok(thriceCompactedText.includes("EVIDENCE-CHUNK-TWO"));
470
+ assert.ok(
471
+ estimateMessageTokens(thriceCompacted) <= 12288,
472
+ "cumulative DeepSeek compaction exceeded the bounded retry target"
473
+ );
474
+
325
475
  console.log("context budget recovery smoke passed");
@@ -838,19 +838,38 @@ function retainedReadRange(payload = {}, args = {}) {
838
838
  };
839
839
  }
840
840
 
841
+ function parseRetainedToolEvidenceMessage(message = {}) {
842
+ if (message?.role !== "user") return null;
843
+ const content = String(message.content || "");
844
+ if (!/^Retained runtime tool evidence\./i.test(content.trim())) return null;
845
+ const match = content.match(
846
+ /^Retained runtime tool evidence\.[^\n]*\nTool:\s*([^\n]+)\nArguments:\s*([^\n]+)\nVerified result:\s*([\s\S]+)$/i
847
+ );
848
+ if (!match) return null;
849
+ const name = String(match[1] || "").trim();
850
+ const args = safeParseToolContent(match[2]) || {};
851
+ const payload = safeParseToolContent(match[3]);
852
+ if (!name || !COMPACTION_STATE_TOOL_NAMES.has(name) || !payload || typeof payload !== "object") {
853
+ return null;
854
+ }
855
+ return { name, args, payload };
856
+ }
857
+
841
858
  function summarizeRetainedSourceEvidence(messages = [], limit = 28) {
842
859
  const bySource = new Map();
843
860
  for (const message of messages) {
844
- if (message?.role !== "tool") continue;
845
- const payload = safeParseToolContent(message.content);
861
+ const retained = parseRetainedToolEvidenceMessage(message);
862
+ if (message?.role !== "tool" && !retained) continue;
863
+ const payload = retained?.payload || safeParseToolContent(message.content);
846
864
  if (!payload || payload.ok === false || payload.blocked || payload.skipped) continue;
847
- const toolName = String(payload.toolName || payload.name || "");
865
+ const toolName = String(retained?.name || payload.toolName || payload.name || "");
848
866
  if (!["read_file", "list_files", "search_files", "inspect_project", "run_command"].includes(toolName)) {
849
867
  continue;
850
868
  }
851
- const sourcePath = String(payload.path || payload.args?.path || "").trim();
852
- const command = String(payload.args?.command || "").trim();
853
- const readRange = toolName === "read_file" ? retainedReadRange(payload, payload.args || {}) : null;
869
+ const args = retained?.args || payload.args || {};
870
+ const sourcePath = String(payload.path || args.path || "").trim();
871
+ const command = String(args.command || "").trim();
872
+ const readRange = toolName === "read_file" ? retainedReadRange(payload, args) : null;
854
873
  const key = `${toolName}:${sourcePath || command}${readRange ? `:${readRange.key}` : ""}`;
855
874
  const parts = [`tool=${toolName}`];
856
875
  if (sourcePath) parts.push(`path=${sourcePath}`);
@@ -976,7 +995,7 @@ function compactRetainedToolPayload(toolName, payload = {}, args = {}) {
976
995
  result.contentTruncatedByLines = payload.contentTruncatedByLines === true;
977
996
  if (payload.sha256) result.sha256 = compactSingleLine(payload.sha256, 96);
978
997
  const content = String(payload.content || payload.contentPreview || "");
979
- if (content) result.content = compactMultiline(content, 4200);
998
+ if (content) result.content = compactMultiline(content, 3000);
980
999
  if (Array.isArray(payload.pathEvidence)) {
981
1000
  result.pathEvidence = compactPathItems(payload.pathEvidence, 12, ["path", "source"]);
982
1001
  }
@@ -986,6 +1005,7 @@ function compactRetainedToolPayload(toolName, payload = {}, args = {}) {
986
1005
  result.results = compactPathItems(payload.results, 24, ["path", "file", "line", "match"]);
987
1006
  } else if (toolName === "run_command") {
988
1007
  result.args = { command: compactMultiline(args.command || payload.args?.command || "", 1200) };
1008
+ if (payload.changed !== undefined) result.changed = Boolean(payload.changed);
989
1009
  if (Number.isFinite(Number(payload.exitCode))) result.exitCode = Number(payload.exitCode);
990
1010
  if (payload.stdout) result.stdout = compactMultiline(payload.stdout, 1800);
991
1011
  if (payload.stderr) result.stderr = compactMultiline(payload.stderr, 1200);
@@ -998,11 +1018,68 @@ function compactRetainedToolPayload(toolName, payload = {}, args = {}) {
998
1018
  return redactValue(result);
999
1019
  }
1000
1020
 
1001
- function retainedToolStateMessages(messages = [], limit = 12) {
1021
+ function retainedPathMatchesOutput(sourcePath = "", outputPaths = []) {
1022
+ const normalize = (value = "") =>
1023
+ String(value || "")
1024
+ .replace(/\\/g, "/")
1025
+ .replace(/^\.\//, "")
1026
+ .replace(/\/{2,}/g, "/")
1027
+ .replace(/\/$/, "");
1028
+ const source = normalize(sourcePath);
1029
+ if (!source) return false;
1030
+ return outputPaths.some((candidate) => {
1031
+ const output = normalize(candidate);
1032
+ return output && (source === output || source.endsWith(`/${output}`) || output.endsWith(`/${source}`));
1033
+ });
1034
+ }
1035
+
1036
+ function retainedToolRecordPriority(record = {}, outputPaths = []) {
1037
+ const name = String(record.name || "");
1038
+ const args = record.args || {};
1039
+ const payload = record.payload || {};
1040
+ let priority = 100;
1041
+ if (name === "deep_research") priority = 1000;
1042
+ else if (name === "inspect_project") priority = 900;
1043
+ else if (["apply_patch", "write_file"].includes(name)) priority = 850;
1044
+ else if (name === "run_command") {
1045
+ const exitCode = Number(payload.exitCode);
1046
+ priority = payload.changed === true || (Number.isFinite(exitCode) && exitCode !== 0) ? 800 : 500;
1047
+ } else if (name === "read_file") {
1048
+ const range = retainedReadRange(payload, args);
1049
+ priority = range.lineLimit > 0 ? 750 : 600;
1050
+ const sourcePath = String(payload.path || args.path || "").trim();
1051
+ if (retainedPathMatchesOutput(sourcePath, outputPaths)) priority -= 220;
1052
+ } else if (["search_files", "list_files"].includes(name)) priority = 400;
1053
+ return priority + Math.min(0.999, Math.max(0, Number(record.ordinal) || 0) / 100000);
1054
+ }
1055
+
1056
+ function retainedToolStateMessages(messages = [], limit = 12, outputPaths = []) {
1002
1057
  const callsById = new Map();
1003
1058
  const recordsByKey = new Map();
1004
1059
  let ordinal = 0;
1060
+ const retainRecord = (name, args = {}, payload = {}) => {
1061
+ if (!COMPACTION_STATE_TOOL_NAMES.has(name)) return;
1062
+ if (!payload || payload.ok === false || payload.blocked || payload.skipped) return;
1063
+ const sourcePath = String(payload.path || args?.path || "").trim();
1064
+ const command = String(args?.command || payload.args?.command || "").trim();
1065
+ const durableIdentity = String(
1066
+ sourcePath || command || payload.researchId || args?.researchId || args?.query || name
1067
+ ).trim();
1068
+ const readRange = name === "read_file" ? retainedReadRange(payload, args) : null;
1069
+ const key = `${name}:${durableIdentity || ordinal}${readRange ? `:${readRange.key}` : ""}`;
1070
+ recordsByKey.set(key, {
1071
+ ordinal: ordinal += 1,
1072
+ name,
1073
+ args: redactValue(args),
1074
+ payload: compactRetainedToolPayload(name, payload, args),
1075
+ });
1076
+ };
1005
1077
  for (const message of messages) {
1078
+ const retained = parseRetainedToolEvidenceMessage(message);
1079
+ if (retained) {
1080
+ retainRecord(retained.name, retained.args, retained.payload);
1081
+ continue;
1082
+ }
1006
1083
  if (message?.role === "assistant" && Array.isArray(message.tool_calls)) {
1007
1084
  for (const call of message.tool_calls) {
1008
1085
  const id = String(call?.id || "").trim();
@@ -1018,27 +1095,19 @@ function retainedToolStateMessages(messages = [], limit = 12) {
1018
1095
  if (message?.role !== "tool") continue;
1019
1096
  const call = callsById.get(String(message.tool_call_id || "").trim());
1020
1097
  const payload = safeParseToolContent(message.content);
1021
- if (!call || !payload || payload.ok === false || payload.blocked || payload.skipped) continue;
1022
- const sourcePath = String(payload.path || call.args?.path || "").trim();
1023
- const command = String(call.args?.command || payload.args?.command || "").trim();
1024
- const readRange = call.name === "read_file" ? retainedReadRange(payload, call.args) : null;
1025
- const key = `${call.name}:${sourcePath || command || ordinal}${readRange ? `:${readRange.key}` : ""}`;
1026
- recordsByKey.set(key, {
1027
- ordinal: ordinal += 1,
1028
- name: call.name,
1029
- args: redactValue(call.args),
1030
- payload: compactRetainedToolPayload(call.name, payload, call.args),
1031
- });
1098
+ if (!call || !payload) continue;
1099
+ retainRecord(call.name, call.args, payload);
1032
1100
  }
1033
1101
 
1034
1102
  const records = [...recordsByKey.values()];
1035
- const pinned = ["inspect_project", "deep_research"]
1036
- .map((name) => [...records].reverse().find((record) => record.name === name))
1037
- .filter(Boolean);
1038
- const remaining = records
1039
- .filter((record) => !pinned.includes(record))
1040
- .slice(-Math.max(0, Number(limit) - pinned.length));
1041
- const selected = [...pinned, ...remaining].sort((left, right) => left.ordinal - right.ordinal);
1103
+ const selected = [...records]
1104
+ .sort(
1105
+ (left, right) =>
1106
+ retainedToolRecordPriority(right, outputPaths) - retainedToolRecordPriority(left, outputPaths) ||
1107
+ right.ordinal - left.ordinal
1108
+ )
1109
+ .slice(0, Math.max(1, Number(limit) || 12))
1110
+ .sort((left, right) => left.ordinal - right.ordinal);
1042
1111
 
1043
1112
  return selected.flatMap((record, index) => {
1044
1113
  const id = `aginti-compacted-tool-${index + 1}`;
@@ -1066,8 +1135,8 @@ function retainedToolStateMessages(messages = [], limit = 12) {
1066
1135
  });
1067
1136
  }
1068
1137
 
1069
- function retainedToolStateTextMessages(messages = [], limit = 12) {
1070
- const nativeMessages = retainedToolStateMessages(messages, limit);
1138
+ function retainedToolStateTextMessages(messages = [], limit = 12, outputPaths = []) {
1139
+ const nativeMessages = retainedToolStateMessages(messages, limit, outputPaths);
1071
1140
  const retained = [];
1072
1141
  for (let index = 0; index < nativeMessages.length; index += 2) {
1073
1142
  const assistantMessage = nativeMessages[index];
@@ -1087,6 +1156,15 @@ function retainedToolStateTextMessages(messages = [], limit = 12) {
1087
1156
  return retained;
1088
1157
  }
1089
1158
 
1159
+ function retainedToolPairPriority(pair = [], order = 0, outputPaths = []) {
1160
+ const assistantCall = pair[0]?.tool_calls?.[0];
1161
+ const retained = pair.length === 1 ? parseRetainedToolEvidenceMessage(pair[0]) : null;
1162
+ const name = String(retained?.name || assistantCall?.function?.name || "");
1163
+ const args = retained?.args || safeParseToolContent(assistantCall?.function?.arguments) || {};
1164
+ const payload = retained?.payload || safeParseToolContent(pair[1]?.content) || {};
1165
+ return retainedToolRecordPriority({ name, args, payload, ordinal: order }, outputPaths);
1166
+ }
1167
+
1090
1168
  function isRuntimeCompactionRequest(content = "") {
1091
1169
  return /^(?:The runtime proactively compacted a long agent history|A previous agent-step model request timed out|Continue from this compacted, valid transcript)/i.test(
1092
1170
  String(content || "").trim()
@@ -1107,6 +1185,7 @@ function summarizeOriginalRequests(messages = [], limit = 6) {
1107
1185
  if (!content.trim()) continue;
1108
1186
  if (/^Step \d+\/\d+ .*Latest runtime snapshot:/i.test(content)) continue;
1109
1187
  if (/^Previous assistant response retained as compacted history/i.test(content)) continue;
1188
+ if (parseRetainedToolEvidenceMessage(message)) continue;
1110
1189
  if (isRuntimeCompactionRequest(content)) continue;
1111
1190
  if (isRuntimeRecoveryRequest(content)) continue;
1112
1191
  requests.push(compactSingleLine(content, 1200));
@@ -1265,9 +1344,10 @@ function buildCompactedRuntimeMessages(state, config, snapshot, step, options =
1265
1344
  // synthetic pairs, so preserve their bounded evidence as explicit runtime
1266
1345
  // context instead of fabricating assistant reasoning.
1267
1346
  const deepSeekCompaction = normalizeProviderId(config.provider, "") === "deepseek";
1347
+ const exactOutputPaths = exactOutputPathsForState(state);
1268
1348
  const retainedToolMessages = deepSeekCompaction
1269
- ? retainedToolStateTextMessages(messages)
1270
- : retainedToolStateMessages(messages);
1349
+ ? retainedToolStateTextMessages(messages, 12, exactOutputPaths)
1350
+ : retainedToolStateMessages(messages, 12, exactOutputPaths);
1271
1351
  const snapshotSummary = {
1272
1352
  step,
1273
1353
  maxSteps: config.maxSteps,
@@ -1345,7 +1425,7 @@ function buildCompactedRuntimeMessages(state, config, snapshot, step, options =
1345
1425
  }));
1346
1426
  const boundedContent = compactTextForTokenBudget(
1347
1427
  compactedContent,
1348
- Math.max(1024, Math.floor(targetTokens * 0.52)),
1428
+ Math.max(1024, Math.floor(targetTokens * (retainedToolMessages.length ? 0.4 : 0.52))),
1349
1429
  { headFraction: 0.58 }
1350
1430
  );
1351
1431
  const baseMessages = [
@@ -1357,20 +1437,29 @@ function buildCompactedRuntimeMessages(state, config, snapshot, step, options =
1357
1437
  ];
1358
1438
  const retainedPairs = [];
1359
1439
  for (let index = 0; index < retainedToolMessages.length; index += deepSeekCompaction ? 1 : 2) {
1360
- retainedPairs.push(retainedToolMessages.slice(index, index + (deepSeekCompaction ? 1 : 2)));
1440
+ const pair = retainedToolMessages.slice(index, index + (deepSeekCompaction ? 1 : 2));
1441
+ retainedPairs.push({
1442
+ pair,
1443
+ order: retainedPairs.length,
1444
+ priority: retainedToolPairPriority(pair, retainedPairs.length, exactOutputPaths),
1445
+ });
1361
1446
  }
1362
1447
  const selectedPairs = [];
1363
- for (const pair of retainedPairs.reverse()) {
1448
+ const prioritizedPairs = [...retainedPairs].sort(
1449
+ (left, right) => right.priority - left.priority || right.order - left.order
1450
+ );
1451
+ for (const candidatePair of prioritizedPairs) {
1364
1452
  const candidate = [
1365
1453
  ...baseMessages,
1366
- ...pair,
1367
- ...selectedPairs.flat(),
1454
+ ...candidatePair.pair,
1455
+ ...selectedPairs.flatMap((item) => item.pair),
1368
1456
  ];
1369
- if (estimateMessageTokens(candidate) <= targetTokens) selectedPairs.unshift(pair);
1457
+ if (estimateMessageTokens(candidate) <= targetTokens) selectedPairs.push(candidatePair);
1370
1458
  }
1459
+ selectedPairs.sort((left, right) => left.order - right.order);
1371
1460
  const compactMessages = [
1372
1461
  ...baseMessages,
1373
- ...selectedPairs.flat(),
1462
+ ...selectedPairs.flatMap((item) => item.pair),
1374
1463
  ];
1375
1464
 
1376
1465
  if (!compactMessages.some((message) => message.role === "system")) {
@@ -373,9 +373,9 @@ function inferExactOutputPaths(goal = "") {
373
373
  "gi"
374
374
  );
375
375
  const directOutputAction =
376
- /\b(save|saved|write|written|output|create|store|update|modify|edit)\b|保存|写入|寫入|输出|輸出|创建|建立|更新|修改|编辑|編輯/i;
376
+ /\b(save|write|rewrite|output|create|rebuild|replace|regenerate|generate|store|update|modify|edit)\b|保存|写入|寫入|重写|重寫|输出|輸出|创建|建立|重建|替换|替換|重新生成|生成|更新|修改|编辑|編輯/i;
377
377
  const directOutputActionGlobal =
378
- /\b(save|saved|write|written|output|create|store|update|modify|edit)\b|保存|写入|寫入|输出|輸出|创建|建立|更新|修改|编辑|編輯/gi;
378
+ /\b(save|write|rewrite|output|create|rebuild|replace|regenerate|generate|store|update|modify|edit)\b|保存|写入|寫入|重写|重寫|输出|輸出|创建|建立|重建|替换|替換|重新生成|生成|更新|修改|编辑|編輯/gi;
379
379
  const outputListHeader =
380
380
  /^(?:#+\s*)?(?:(?:required|final|expected|declared|target|pilot|deliverable)\s+)*(?:create|created files?|files? to create|outputs?|output structure|required outputs?|artifacts?|deliverables?|generated files?|writer requirements|renderer requirements|生成文件|输出结构|輸出結構|输出文件|輸出文件|创建文件|建立文件)(?:\s+(?:outputs?|artifacts?|deliverables?))?\s*[::]?\s*$/i;
381
381
  const nonOutputToolLine =
@@ -470,7 +470,7 @@ function inferExactInputPaths(goal = "") {
470
470
  const inputAction =
471
471
  /\b(use|using|read|load|fill|upload|attach|import|select|choose|reference|input|from|fix|repair|patch|correct)\b|使用|读取|讀取|加载|載入|填写|填入|上传|上傳|附加|导入|導入|选择|選擇|选取|選取|参考|參考|素材|图片|圖片|照片|提示词|提示詞|修复|修正|更正|从|從/i;
472
472
  const directOutputAction =
473
- /\b(save|saved|write|written|output|create|store|update|modify|edit)\b|保存|写入|寫入|输出|輸出|创建|建立|更新|修改|编辑|編輯/i;
473
+ /\b(save|write|rewrite|output|create|rebuild|replace|regenerate|generate|store|update|modify|edit)\b|保存|写入|寫入|重写|重寫|输出|輸出|创建|建立|重建|替换|替換|重新生成|生成|更新|修改|编辑|編輯/i;
474
474
  const pushPath = (raw = "") => {
475
475
  const cleaned = String(raw || "").trim();
476
476
  if (!cleaned || /[{}]/.test(cleaned)) return;