@lazyingart/agintiflow 0.20.88 → 0.20.90

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.88",
3
+ "version": "0.20.90",
4
4
  "type": "module",
5
5
  "description": "Low-cost, project-aware Web and CLI agents with DeepSeek/Venice/OpenAI routing, visible tool calls, durable sessions, scouts, AAPS, SCS, and guarded local execution.",
6
6
  "license": "Apache-2.0",
@@ -276,6 +276,30 @@ try {
276
276
  );
277
277
  assert(readonlyProbePolicy.allowed, "read-only toolchain probe sequence should not require package-install-policy=allow");
278
278
  assert(readonlyProbePolicy.category === "read-only", "read-only toolchain probe sequence should be classified as read-only");
279
+ const readonlyVersionPipelinePolicy = evaluateCommandPolicy(
280
+ "which pdflatex latexmk python3 2>&1; pdflatex --version 2>&1 | head -2; latexmk --version 2>&1 | head -2",
281
+ dockerWorkspaceNoInstallsPolicy
282
+ );
283
+ assert(readonlyVersionPipelinePolicy.allowed, "read-only version probe pipelines should not require package-install-policy=allow");
284
+ assert(readonlyVersionPipelinePolicy.category === "read-only", "read-only version probe pipelines should be classified as read-only");
285
+ const readonlyTestEchoPolicy = evaluateCommandPolicy(
286
+ 'test -f /usr/bin/pdflatex && echo "pdflatex: FOUND" || echo "pdflatex: NOT FOUND"; test -f /usr/bin/latexmk && echo "latexmk: FOUND" || echo "latexmk: NOT FOUND"; python3 --version 2>&1',
287
+ dockerWorkspaceNoInstallsPolicy
288
+ );
289
+ assert(readonlyTestEchoPolicy.allowed, "read-only test/echo probe sequence should not require package-install-policy=allow");
290
+ assert(readonlyTestEchoPolicy.category === "read-only", "read-only test/echo probe sequence should be classified as read-only");
291
+ const pdflatexCompilePolicy = evaluateCommandPolicy(
292
+ 'cd profile-latex-20260506 && pdflatex -interaction=nonstopmode -halt-on-error main.tex 2>&1; echo "PDFLATEX_EXIT:$?"',
293
+ dockerWorkspaceNoInstallsPolicy
294
+ );
295
+ assert(pdflatexCompilePolicy.allowed, "workspace-local pdflatex compile should be allowed without package installs");
296
+ assert(pdflatexCompilePolicy.category === "toolchain", "workspace-local pdflatex compile should be classified as toolchain");
297
+ const latexmkCompilePolicy = evaluateCommandPolicy(
298
+ 'cd profile-latex-20260506 && latexmk -pdf main.tex 2>&1; echo "LATEXMK_EXIT:$?"',
299
+ dockerWorkspaceNoInstallsPolicy
300
+ );
301
+ assert(latexmkCompilePolicy.allowed, "workspace-local latexmk compile should be allowed without package installs");
302
+ assert(latexmkCompilePolicy.category === "toolchain", "workspace-local latexmk compile should be classified as toolchain");
279
303
  const curlPolicy = evaluateCommandPolicy("curl -s -o /dev/null -w '%{http_code}' https://github.com/lazyingart/AgInTiFlow.git", dockerWorkspacePolicy);
280
304
  assert(curlPolicy.allowed, "curl URL probe with flags should be allowed in docker-workspace allow mode");
281
305
  assert(curlPolicy.needsNetwork, "curl URL probe with flags was not classified as network");
@@ -736,6 +760,10 @@ try {
736
760
  "auto_system_pro_route",
737
761
  "auto_engineering_guidance",
738
762
  "command_policy_readonly_probe_sequence_no_installs",
763
+ "command_policy_readonly_version_pipeline_no_installs",
764
+ "command_policy_readonly_test_echo_no_installs",
765
+ "command_policy_pdflatex_compile_no_installs",
766
+ "command_policy_latexmk_compile_no_installs",
739
767
  "command_policy_git_clone_network",
740
768
  "command_policy_safe_chmod_sequence",
741
769
  "command_policy_cd_workspace",
@@ -23,6 +23,10 @@ const READ_ONLY_PATTERNS = [
23
23
  /^python(?:3)?\s+--version$/,
24
24
  /^pip(?:3)?\s+--version$/,
25
25
  /^conda\s+--version$/,
26
+ /^(?:pdflatex|latexmk)\s+--version$/,
27
+ /^test\s+-[efdx]\s+[-\w./~]+$/,
28
+ /^true$/,
29
+ /^false$/,
26
30
  /^echo(?:\s+.+)?$/,
27
31
  ];
28
32
 
@@ -261,7 +265,7 @@ function classifyGitClone(normalized) {
261
265
  }
262
266
 
263
267
  function classifySimpleCommand(normalized) {
264
- const readOnlyCommand = stripBenignRedirections(normalized);
268
+ const benignRedirectCommand = stripBenignRedirections(normalized);
265
269
  if (ALWAYS_BLOCKED_PATTERNS.some((pattern) => pattern.test(normalized))) {
266
270
  return { category: "blocked", reason: "Command is blocked because it may expose secrets or publish packages." };
267
271
  }
@@ -314,7 +318,7 @@ function classifySimpleCommand(normalized) {
314
318
  const gitCloneClassification = classifyGitClone(normalized);
315
319
  if (gitCloneClassification) return gitCloneClassification;
316
320
 
317
- const unquoted = stripQuotedSegments(readOnlyCommand);
321
+ const unquoted = stripQuotedSegments(benignRedirectCommand);
318
322
  const lowered = ` ${unquoted.toLowerCase()} `;
319
323
  if (BLOCKED_WRITE_TOKENS.some((part) => lowered.includes(part))) {
320
324
  return {
@@ -333,25 +337,25 @@ function classifySimpleCommand(normalized) {
333
337
  };
334
338
  }
335
339
 
336
- if (matchAny(READ_ONLY_PATTERNS, readOnlyCommand) || isReadOnlyFindCommand(normalized)) {
340
+ if (matchAny(READ_ONLY_PATTERNS, benignRedirectCommand) || isReadOnlyFindCommand(normalized)) {
337
341
  return { category: "read-only", needsNetwork: false, writesWorkspace: false };
338
342
  }
339
- if (matchAny(TEST_PATTERNS, normalized)) {
343
+ if (matchAny(TEST_PATTERNS, benignRedirectCommand)) {
340
344
  return { category: "test", needsNetwork: false, writesWorkspace: false };
341
345
  }
342
- if (matchAny(TOOLCHAIN_PATTERNS, normalized)) {
346
+ if (matchAny(TOOLCHAIN_PATTERNS, benignRedirectCommand)) {
343
347
  return { category: "toolchain", needsNetwork: false, writesWorkspace: true };
344
348
  }
345
- if (matchAny(NETWORK_FETCH_PATTERNS, normalized)) {
346
- return { category: "network-fetch", needsNetwork: true, writesWorkspace: /(\s-o\s|\s-O\s)/.test(normalized) };
349
+ if (matchAny(NETWORK_FETCH_PATTERNS, benignRedirectCommand)) {
350
+ return { category: "network-fetch", needsNetwork: true, writesWorkspace: /(\s-o\s|\s-O\s)/.test(benignRedirectCommand) };
347
351
  }
348
- if (matchAny(SYSTEM_PACKAGE_INSTALL_PATTERNS, normalized)) {
352
+ if (matchAny(SYSTEM_PACKAGE_INSTALL_PATTERNS, benignRedirectCommand)) {
349
353
  return { category: "system-package-install", needsNetwork: true, writesWorkspace: false, requiresDockerRoot: true };
350
354
  }
351
- if (matchAny(PACKAGE_INSTALL_PATTERNS, normalized)) {
355
+ if (matchAny(PACKAGE_INSTALL_PATTERNS, benignRedirectCommand)) {
352
356
  return { category: "package-install", needsNetwork: true, writesWorkspace: true };
353
357
  }
354
- if (matchAny(ENV_SETUP_PATTERNS, normalized)) {
358
+ if (matchAny(ENV_SETUP_PATTERNS, benignRedirectCommand)) {
355
359
  return { category: "env-setup", needsNetwork: false, writesWorkspace: true };
356
360
  }
357
361
 
@@ -392,13 +396,13 @@ function splitTopLevelShellSequence(command = "") {
392
396
  quote = char;
393
397
  continue;
394
398
  }
395
- if (char === ";" || (char === "&" && text[index + 1] === "&")) {
399
+ if (char === ";" || (char === "&" && text[index + 1] === "&") || (char === "|" && text[index + 1] === "|")) {
396
400
  const part = current.trim();
397
401
  if (!part) return null;
398
402
  parts.push(part);
399
403
  current = "";
400
404
  hadSeparator = true;
401
- if (char === "&") index += 1;
405
+ if (char === "&" || char === "|") index += 1;
402
406
  continue;
403
407
  }
404
408
  current += char;
@@ -412,7 +416,7 @@ function splitTopLevelShellSequence(command = "") {
412
416
  function classifyShellSequence(normalized) {
413
417
  const parts = splitTopLevelShellSequence(normalized);
414
418
  if (!parts) return null;
415
- const classifications = parts.map((part) => classifyCdCommand(part) || classifySimpleCommand(part));
419
+ const classifications = parts.map((part) => classifyCdCommand(part) || classifyPipelineSequence(part) || classifySimpleCommand(part));
416
420
  const blocked = classifications.find((classification) => classification.category === "blocked" || classification.category === "destructive");
417
421
  if (blocked) return blocked;
418
422
  const broad = classifications.find((classification) => classification.category === "general-shell");
@@ -446,6 +450,73 @@ function classifyShellSequence(normalized) {
446
450
  };
447
451
  }
448
452
 
453
+ function splitTopLevelPipeline(command = "") {
454
+ const parts = [];
455
+ let current = "";
456
+ let quote = "";
457
+ let escaped = false;
458
+ let hadPipe = false;
459
+ const text = String(command || "");
460
+ for (let index = 0; index < text.length; index += 1) {
461
+ const char = text[index];
462
+ if (escaped) {
463
+ current += char;
464
+ escaped = false;
465
+ continue;
466
+ }
467
+ if (char === "\\") {
468
+ current += char;
469
+ escaped = true;
470
+ continue;
471
+ }
472
+ if (quote) {
473
+ current += char;
474
+ if (char === quote) quote = "";
475
+ continue;
476
+ }
477
+ if (char === "'" || char === '"') {
478
+ current += char;
479
+ quote = char;
480
+ continue;
481
+ }
482
+ if (char === "|" && text[index + 1] !== "|") {
483
+ const part = current.trim();
484
+ if (!part) return null;
485
+ parts.push(part);
486
+ current = "";
487
+ hadPipe = true;
488
+ continue;
489
+ }
490
+ current += char;
491
+ }
492
+ const finalPart = current.trim();
493
+ if (finalPart) parts.push(finalPart);
494
+ if (!hadPipe || parts.length < 2) return null;
495
+ return parts;
496
+ }
497
+
498
+ function classifyPipelineSequence(normalized) {
499
+ const parts = splitTopLevelPipeline(normalized);
500
+ if (!parts) return null;
501
+ const classifications = parts.map((part) => classifyCdCommand(part) || classifySimpleCommand(part));
502
+ const blocked = classifications.find((classification) => classification.category === "blocked" || classification.category === "destructive");
503
+ if (blocked) return blocked;
504
+ if (classifications.every((classification) => classification.category === "read-only")) {
505
+ return {
506
+ category: "read-only",
507
+ needsNetwork: false,
508
+ writesWorkspace: false,
509
+ reason: `Read-only shell pipeline: ${normalized}`,
510
+ };
511
+ }
512
+ return {
513
+ category: "general-shell",
514
+ needsNetwork: classifications.some((classification) => classification.needsNetwork),
515
+ writesWorkspace: classifications.some((classification) => classification.writesWorkspace),
516
+ reason: `Shell pipeline includes a broad segment and requires trusted shell policy: ${normalized}`,
517
+ };
518
+ }
519
+
449
520
  function classifyCdCommand(normalized) {
450
521
  const match = normalized.match(/^cd\s+([-\w./]+)\s+&&\s+(.+)$/);
451
522
  if (!match) return null;
@@ -454,7 +525,7 @@ function classifyCdCommand(normalized) {
454
525
  if (!isSafeRelativeDir(dir) && !virtualWorkspacePath) {
455
526
  return { category: "blocked", reason: `cd target must be a safe workspace-relative directory: ${dir}` };
456
527
  }
457
- const innerClassification = classifySimpleCommand(inner.trim());
528
+ const innerClassification = classifyShellSequence(inner.trim()) || classifyPipelineSequence(inner.trim()) || classifySimpleCommand(inner.trim());
458
529
  if (innerClassification.category === "blocked") return innerClassification;
459
530
  return { ...innerClassification, cdDir: dir, virtualWorkspacePath };
460
531
  }
@@ -463,7 +534,7 @@ export function classifyCommand(command) {
463
534
  const normalized = String(command || "").trim();
464
535
  if (!normalized) return { category: "blocked", reason: "Command is empty." };
465
536
 
466
- return classifyCdCommand(normalized) || classifyShellSequence(normalized) || classifySimpleCommand(normalized);
537
+ return classifyCdCommand(normalized) || classifyShellSequence(normalized) || classifyPipelineSequence(normalized) || classifySimpleCommand(normalized);
467
538
  }
468
539
 
469
540
  export function evaluateCommandPolicy(command, config) {