@lazyingart/agintiflow 0.20.314 → 0.20.315

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.314",
3
+ "version": "0.20.315",
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",
@@ -6030,13 +6030,86 @@ try {
6030
6030
  sandboxMode: "host",
6031
6031
  }
6032
6032
  );
6033
+ const scopedCompoundTestLogCommand = {
6034
+ toolName: "run_command",
6035
+ ok: true,
6036
+ exitCode: 0,
6037
+ args: {
6038
+ command: [
6039
+ `cd ${JSON.stringify(workspace)}`,
6040
+ `LOG=${JSON.stringify(path.join(scopedTaskRoot, "document-reader-test.log"))}`,
6041
+ 'echo "RUN_MARKER=now" > "$LOG"',
6042
+ 'python3 -m unittest discover -s tests -p \'test_reader.py\' -v >> "$LOG" 2>&1',
6043
+ 'echo "EXIT=0" >> "$LOG"',
6044
+ 'cat "$LOG"',
6045
+ ].join(" && "),
6046
+ },
6047
+ projectMutationPaths: [],
6048
+ stdout: "Ran 7 tests\nOK\nEXIT=0\n",
6049
+ stderr: "",
6050
+ };
6051
+ recordProjectVerificationOutcome(
6052
+ scopedTaskState,
6053
+ scopedCompoundTestLogCommand,
6054
+ {
6055
+ commandCwd: workspace,
6056
+ taskProfile: "auto",
6057
+ allowShellTool: true,
6058
+ sandboxMode: "host",
6059
+ }
6060
+ );
6061
+ const nullRedirectValidationCommand = {
6062
+ toolName: "run_command",
6063
+ ok: true,
6064
+ exitCode: 0,
6065
+ args: {
6066
+ command: [
6067
+ `cd ${JSON.stringify(workspace)}`,
6068
+ `python3 -m json.tool ${JSON.stringify(
6069
+ path.join(scopedTaskRoot, "agent-result.json")
6070
+ )} > /dev/null`,
6071
+ 'echo "JSON_VALID"',
6072
+ `grep -c '^## [0-9]' ${JSON.stringify(
6073
+ path.join(scopedTaskRoot, "source-intake-audit.md")
6074
+ )}`,
6075
+ ].join(" && "),
6076
+ },
6077
+ projectMutationPaths: [],
6078
+ stdout: "JSON_VALID\n5\n",
6079
+ stderr: "",
6080
+ };
6081
+ recordProjectVerificationOutcome(
6082
+ scopedTaskState,
6083
+ nullRedirectValidationCommand,
6084
+ {
6085
+ commandCwd: workspace,
6086
+ taskProfile: "auto",
6087
+ allowShellTool: true,
6088
+ sandboxMode: "host",
6089
+ }
6090
+ );
6033
6091
  assert(
6034
6092
  requestedOutputRevision === 1 &&
6035
6093
  scopedTaskState.meta.projectVerification?.mutationRevision === 1 &&
6036
6094
  readOnlyArtifactValidation.readOnlyArtifactValidation === true &&
6037
6095
  scopedManifestCommand.scopedTaskArtifactWrite === true &&
6038
- pythonScopedManifestCommand.scopedTaskArtifactWrite === true,
6039
- "task-scoped delivery bookkeeping invalidated requested artifact evidence"
6096
+ pythonScopedManifestCommand.scopedTaskArtifactWrite === true &&
6097
+ scopedCompoundTestLogCommand.scopedTaskArtifactWrite === true &&
6098
+ nullRedirectValidationCommand.readOnlyArtifactValidation === true,
6099
+ `task-scoped delivery bookkeeping invalidated requested artifact evidence: ${JSON.stringify({
6100
+ requestedOutputRevision,
6101
+ mutationRevision:
6102
+ scopedTaskState.meta.projectVerification?.mutationRevision,
6103
+ readOnlyArtifactValidation:
6104
+ readOnlyArtifactValidation.readOnlyArtifactValidation,
6105
+ scopedManifestCommand: scopedManifestCommand.scopedTaskArtifactWrite,
6106
+ pythonScopedManifestCommand:
6107
+ pythonScopedManifestCommand.scopedTaskArtifactWrite,
6108
+ scopedCompoundTestLogCommand:
6109
+ scopedCompoundTestLogCommand.scopedTaskArtifactWrite,
6110
+ nullRedirectValidationCommand:
6111
+ nullRedirectValidationCommand.readOnlyArtifactValidation,
6112
+ })}`
6040
6113
  );
6041
6114
  const mutatingInlinePython = {
6042
6115
  toolName: "run_command",
@@ -12762,31 +12762,85 @@ function safePatchContextEvidencePath(value = "", state = {}, config = {}) {
12762
12762
  function commandWritesOnlyEvidenceMatching(command = "", pathMatches = () => false) {
12763
12763
  const normalized = String(command || "").trim();
12764
12764
  if (!normalized) return false;
12765
+ const assignments = new Map();
12766
+ for (const match of normalized.matchAll(
12767
+ /(?:^|[\s;&|])([A-Za-z_]\w*)\s*=\s*("[^"\n]+"|'[^'\n]+'|[^\s;&|]+)/g
12768
+ )) {
12769
+ assignments.set(
12770
+ match[1],
12771
+ String(match[2] || "").replace(/^["']|["']$/g, "")
12772
+ );
12773
+ }
12774
+ const resolveTarget = (value = "") => {
12775
+ const target = String(value || "")
12776
+ .trim()
12777
+ .replace(/^["']|["']$/g, "");
12778
+ const variable = target.match(/^\$(?:\{([A-Za-z_]\w*)\}|([A-Za-z_]\w*))$/);
12779
+ return variable
12780
+ ? String(assignments.get(variable[1] || variable[2]) || "")
12781
+ : target;
12782
+ };
12765
12783
  const tokens = tokenizeShellWords(normalized);
12766
12784
  if (tokens[0] === "tee") {
12767
12785
  let index = 1;
12768
12786
  if (["-a", "--append"].includes(tokens[index])) index += 1;
12769
12787
  if (tokens[index] === "--") index += 1;
12770
- const targets = tokens.slice(index);
12788
+ const targets = tokens.slice(index).map(resolveTarget);
12771
12789
  return targets.length > 0 && targets.every(pathMatches);
12772
12790
  }
12773
12791
  if (tokens[0] === "mkdir") {
12774
- const targets = tokens.slice(1).filter((token) => token !== "-p" && token !== "--");
12792
+ const targets = tokens
12793
+ .slice(1)
12794
+ .filter((token) => token !== "-p" && token !== "--")
12795
+ .map(resolveTarget);
12775
12796
  return targets.length > 0 && targets.every(pathMatches);
12776
12797
  }
12777
12798
 
12778
12799
  let strippedEvidenceRedirect = false;
12800
+ let rejectedRedirect = false;
12779
12801
  const withoutEvidenceRedirects = normalized.replace(
12780
12802
  /(^|\s)(?:\d*>>?|&>>?)\s*("[^"]+"|'[^']+'|[^\s;&|]+)/g,
12781
12803
  (match, prefix, target) => {
12782
- if (!pathMatches(target)) return match;
12804
+ const resolvedTarget = resolveTarget(target);
12805
+ if (/^(?:&\d+|\/dev\/null)$/u.test(resolvedTarget)) return prefix;
12806
+ if (!resolvedTarget || !pathMatches(resolvedTarget)) {
12807
+ rejectedRedirect = true;
12808
+ return match;
12809
+ }
12783
12810
  strippedEvidenceRedirect = true;
12784
12811
  return prefix;
12785
12812
  }
12786
12813
  ).trim();
12787
- if (!strippedEvidenceRedirect || !withoutEvidenceRedirects) return false;
12788
- const underlyingPolicy = classifyCommand(withoutEvidenceRedirects);
12789
- return underlyingPolicy.writesWorkspace !== true && underlyingPolicy.mayMutateProject !== true;
12814
+ if (rejectedRedirect || !strippedEvidenceRedirect || !withoutEvidenceRedirects) {
12815
+ return false;
12816
+ }
12817
+ const sequence = parseTopLevelShellSequence(withoutEvidenceRedirects);
12818
+ if (sequence.openQuote || sequence.trailingEscape || sequence.trailingSeparator) {
12819
+ return false;
12820
+ }
12821
+ return sequence.commands.every((segment) => {
12822
+ const withoutAssignments = String(segment || "")
12823
+ .trim()
12824
+ .replace(
12825
+ /^(?:[A-Za-z_]\w*=(?:"[^"\n]*"|'[^'\n]*'|[^\s;&|]+)\s*)+/u,
12826
+ ""
12827
+ )
12828
+ .trim();
12829
+ if (!withoutAssignments) return true;
12830
+ const commandTokens = tokenizeShellWords(withoutAssignments);
12831
+ if (
12832
+ ["cat", "cd", "head", "ls", "stat", "tail", "wc"].includes(
12833
+ String(commandTokens[0] || "")
12834
+ )
12835
+ ) {
12836
+ return true;
12837
+ }
12838
+ const underlyingPolicy = classifyCommand(withoutAssignments);
12839
+ return (
12840
+ underlyingPolicy.writesWorkspace !== true &&
12841
+ underlyingPolicy.mayMutateProject !== true
12842
+ );
12843
+ });
12790
12844
  }
12791
12845
 
12792
12846
  function commandWritesOnlyPrivateVerificationEvidence(command = "") {
@@ -12833,15 +12887,20 @@ function commandIsBoundedReadOnlyArtifactValidation(command = "") {
12833
12887
  const normalized = String(command || "").trim();
12834
12888
  if (!normalized) return false;
12835
12889
  if (
12836
- !/\bassert\b|\bjson\.(?:load|loads)\s*\(|\bJSON\.parse\s*\(|\bjq\b|\bsha256sum\b|\bmd5sum\b|\b(?:valid|pass|verified?)\b/i.test(
12890
+ !/\bassert\b|\bjson\.(?:load|loads|tool)\b|\bJSON\.parse\s*\(|\bjq\b|\bsha256sum\b|\bmd5sum\b|\b(?:valid|pass|verified?)\b/i.test(
12837
12891
  normalized
12838
12892
  )
12839
12893
  ) {
12840
12894
  return false;
12841
12895
  }
12896
+ const withoutNullRedirects = normalized.replace(
12897
+ /(^|\s)(?:\d*>>?|&>>?)\s*(?:"\/dev\/null"|'\/dev\/null'|\/dev\/null)/g,
12898
+ "$1"
12899
+ );
12842
12900
  // Heredoc input (`<<`) is compatible with read-only validation. Any output
12843
- // redirection or known mutator keeps conservative project revision tracking.
12844
- if (/(^|[^<])>{1,2}(?!=)/m.test(normalized)) return false;
12901
+ // redirection outside /dev/null or known mutator keeps conservative project
12902
+ // revision tracking.
12903
+ if (/(^|[^<])>{1,2}(?!=)/m.test(withoutNullRedirects)) return false;
12845
12904
  if (
12846
12905
  /\b(?:cp|install|mkdir|mv|rm|rmdir|tee|touch|truncate)\b|\bsed\s+-i\b|\bperl\s+-pi\b/i.test(
12847
12906
  normalized