@lazyingart/agintiflow 0.20.88 → 0.20.89

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.89",
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,18 @@ 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");
279
291
  const curlPolicy = evaluateCommandPolicy("curl -s -o /dev/null -w '%{http_code}' https://github.com/lazyingart/AgInTiFlow.git", dockerWorkspacePolicy);
280
292
  assert(curlPolicy.allowed, "curl URL probe with flags should be allowed in docker-workspace allow mode");
281
293
  assert(curlPolicy.needsNetwork, "curl URL probe with flags was not classified as network");
@@ -736,6 +748,8 @@ try {
736
748
  "auto_system_pro_route",
737
749
  "auto_engineering_guidance",
738
750
  "command_policy_readonly_probe_sequence_no_installs",
751
+ "command_policy_readonly_version_pipeline_no_installs",
752
+ "command_policy_readonly_test_echo_no_installs",
739
753
  "command_policy_git_clone_network",
740
754
  "command_policy_safe_chmod_sequence",
741
755
  "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
 
@@ -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) {