@lazyingart/agintiflow 0.20.136 → 0.20.138

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.136",
3
+ "version": "0.20.138",
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",
@@ -681,6 +681,20 @@ try {
681
681
  );
682
682
  assert(limitedReadResult.content === "line 001\nline 002\nline 003", "read_file lineLimit did not return the requested line slice");
683
683
  assert(limitedReadResult.contentTruncatedByLines === true, "read_file lineLimit should report remaining lines");
684
+ const largeReadLines = Array.from({ length: 30000 }, (_, index) => `large line ${String(index + 1).padStart(5, "0")}`).join("\n");
685
+ await fs.writeFile(path.join(workspace, "large-read-smoke.md"), largeReadLines, "utf8");
686
+ const largeLimitedReadResult = await executeWorkspaceTool(
687
+ "read_file",
688
+ { path: "large-read-smoke.md", startLine: 100, lineLimit: 2 },
689
+ {
690
+ commandCwd: workspace,
691
+ allowFileTools: true,
692
+ }
693
+ );
694
+ assert(
695
+ largeLimitedReadResult.content === "large line 00100\nlarge line 00101",
696
+ "read_file lineLimit should work on larger text files"
697
+ );
684
698
  const inspected = await executeWorkspaceTool(
685
699
  "inspect_project",
686
700
  { path: ".", maxDepth: 4, limit: 200 },
@@ -1017,6 +1031,27 @@ try {
1017
1031
  if (error.code !== "ENOENT") throw error;
1018
1032
  });
1019
1033
 
1034
+ const safeEnvReferenceResult = await executeWorkspaceTool(
1035
+ "write_file",
1036
+ {
1037
+ path: "scripts/safe-env-reference.py",
1038
+ content: [
1039
+ "import os",
1040
+ "from openai import OpenAI",
1041
+ 'api_key = os.environ.get("DEEPSEEK_API_KEY")',
1042
+ 'client = OpenAI(api_key=api_key, base_url="https://api.deepseek.com")',
1043
+ "print(client)",
1044
+ "",
1045
+ ].join("\n"),
1046
+ mode: "create",
1047
+ },
1048
+ {
1049
+ commandCwd: workspace,
1050
+ allowFileTools: true,
1051
+ }
1052
+ );
1053
+ assert(safeEnvReferenceResult.ok, "write_file should allow safe env-var credential references in source code");
1054
+
1020
1055
  const redactedContentResult = await executeWorkspaceTool(
1021
1056
  "write_file",
1022
1057
  {
@@ -1113,6 +1148,7 @@ try {
1113
1148
  "patch_move_no_overwrite",
1114
1149
  "block_env",
1115
1150
  "block_secret_write_content",
1151
+ "allow_safe_env_reference_content",
1116
1152
  "allow_redacted_write_content",
1117
1153
  "block_secret_patch_content",
1118
1154
  "block_outside",
@@ -7,6 +7,7 @@ export const WORKSPACE_TOOL_NAMES = ["inspect_project", "list_files", "read_file
7
7
  export const WORKSPACE_WRITE_TOOL_NAMES = ["write_file", "apply_patch"];
8
8
 
9
9
  const MAX_READ_BYTES = 220_000;
10
+ const MAX_LINE_LIMIT_READ_BYTES = 5_000_000;
10
11
  const MAX_WRITE_BYTES = 220_000;
11
12
  const MAX_PATCH_BYTES = 260_000;
12
13
  const MAX_LIST_ENTRIES = 360;
@@ -197,7 +198,7 @@ function pathPolicy(toolName, relativePath) {
197
198
  }
198
199
 
199
200
  function secretContentPolicy(content) {
200
- if (!hasSensitiveText(content)) return { allowed: true };
201
+ if (!hasSensitiveText(stripSafeCredentialReferences(content))) return { allowed: true };
201
202
  return {
202
203
  allowed: false,
203
204
  reason:
@@ -206,6 +207,35 @@ function secretContentPolicy(content) {
206
207
  };
207
208
  }
208
209
 
210
+ function stripSafeCredentialReferences(content) {
211
+ let text = String(content ?? "");
212
+ const keyName = String.raw`(?:api[_-]?key|token|secret|password|passwd|npm_token|_authToken|grsai|venice_api_key)`;
213
+ const envName = String.raw`[A-Z][A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|GRSAI|VENICE)[A-Z0-9_]*`;
214
+
215
+ text = text.replace(
216
+ new RegExp(String.raw`\b${keyName}\s*=\s*(?:os\.environ\.get|os\.getenv)\(\s*["']${envName}["'](?:\s*,\s*(?:None|""|''))?\s*\)`, "gi"),
217
+ "safe_credential_ref=[ENV_REF]"
218
+ );
219
+ text = text.replace(
220
+ new RegExp(String.raw`\b${keyName}\s*=\s*(?:os\.environ|env)\[\s*["']${envName}["']\s*\]`, "gi"),
221
+ "safe_credential_ref=[ENV_REF]"
222
+ );
223
+ text = text.replace(
224
+ new RegExp(String.raw`\b${keyName}\s*=\s*(?:process\.env\.${envName}|Deno\.env\.get\(\s*["']${envName}["']\s*\))`, "gi"),
225
+ "safe_credential_ref=[ENV_REF]"
226
+ );
227
+ text = text.replace(
228
+ new RegExp(String.raw`([,(]\s*)${keyName}\s*=\s*[A-Za-z_$][\w$]*(?=\s*[,)\n])`, "gi"),
229
+ "$1safe_credential_ref=[VAR_REF]"
230
+ );
231
+ text = text.replace(
232
+ new RegExp(String.raw`([,{]\s*)${keyName}\s*:\s*[A-Za-z_$][\w$]*(?=\s*[,}\n])`, "gi"),
233
+ "$1safe_credential_ref=[VAR_REF]"
234
+ );
235
+
236
+ return text;
237
+ }
238
+
209
239
  function writeContentPolicy(toolName, args, operations = null) {
210
240
  if (toolName === "write_file") return secretContentPolicy(args.content || "");
211
241
  if (toolName !== "apply_patch") return { allowed: true };
@@ -390,10 +420,14 @@ async function listFiles(config, args) {
390
420
  };
391
421
  }
392
422
 
393
- async function readTextFile(target) {
423
+ async function readTextFile(target, { allowLargeLineLimited = false } = {}) {
394
424
  const stat = await fs.stat(target.absolutePath);
395
425
  if (!stat.isFile()) throw new Error(`Path is not a file: ${target.relativePath}`);
396
- if (stat.size > MAX_READ_BYTES) throw new Error(`File is too large to read safely: ${target.relativePath}`);
426
+ if (stat.size > MAX_READ_BYTES) {
427
+ if (!allowLargeLineLimited || stat.size > MAX_LINE_LIMIT_READ_BYTES) {
428
+ throw new Error(`File is too large to read safely: ${target.relativePath}`);
429
+ }
430
+ }
397
431
 
398
432
  const buffer = await fs.readFile(target.absolutePath);
399
433
  if (buffer.includes(0)) throw new Error(`Binary files are not readable through this tool: ${target.relativePath}`);
@@ -406,11 +440,11 @@ async function readTextFile(target) {
406
440
 
407
441
  async function readFile(config, args) {
408
442
  const target = resolveWorkspacePath(config, args.path);
409
- const { stat, content, hash } = await readTextFile(target);
410
- const lines = content.split(/\r?\n/);
411
- const startLine = Math.min(Math.max(Number(args.startLine) || 1, 1), Math.max(lines.length, 1));
412
443
  const requestedLineLimit = Number(args.lineLimit || args.limit || 0);
413
444
  const lineLimit = Number.isFinite(requestedLineLimit) && requestedLineLimit > 0 ? Math.min(requestedLineLimit, 1000) : 0;
445
+ const { stat, content, hash } = await readTextFile(target, { allowLargeLineLimited: lineLimit > 0 });
446
+ const lines = content.split(/\r?\n/);
447
+ const startLine = Math.min(Math.max(Number(args.startLine) || 1, 1), Math.max(lines.length, 1));
414
448
  const selectedLines = lineLimit > 0 ? lines.slice(startLine - 1, startLine - 1 + lineLimit) : lines;
415
449
  const selectedContent = selectedLines.join("\n");
416
450
  return {