network-ai 3.1.0 → 3.1.2

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/SKILL.md CHANGED
@@ -1,7 +1,14 @@
1
1
  ---
2
- name: swarm-orchestrator
3
- description: Multi-agent swarm orchestration for complex workflows. Use when coordinating multiple agents, delegating tasks between sessions, managing shared state via blackboard, or enforcing permission walls for DATABASE/PAYMENTS/EMAIL access. Triggers on: agent coordination, task delegation, parallel execution, permission requests, blackboard state management.
4
- metadata: { "openclaw": { "emoji": "🐝", "homepage": "https://github.com/jovanSAPFIONEER/Network-AI" } }
2
+ name: Network-AI
3
+ description: Multi-agent swarm orchestration for complex workflows. Coordinates multiple agents, decomposes tasks, manages shared state via a local blackboard file, and enforces permission walls before sensitive operations. All execution is local and sandboxed.
4
+ metadata:
5
+ openclaw:
6
+ emoji: "\U0001F41D"
7
+ homepage: https://github.com/jovanSAPFIONEER/Network-AI
8
+ requires:
9
+ bins:
10
+ - python3
11
+ - node
5
12
  ---
6
13
 
7
14
  # Swarm Orchestrator Skill
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "network-ai",
3
- "version": "3.1.0",
3
+ "version": "3.1.2",
4
4
  "description": "AI agent orchestration framework for TypeScript/Node.js - plug-and-play multi-agent coordination with 12 frameworks (LangChain, AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, OpenClaw). Built-in security, swarm intelligence, and agentic workflow patterns.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -307,7 +307,38 @@ Last Updated: {datetime.now(timezone.utc).isoformat()}
307
307
  # ========================================================================
308
308
  # ATOMIC COMMIT WORKFLOW: propose → validate → commit
309
309
  # ========================================================================
310
-
310
+
311
+ @staticmethod
312
+ def _sanitize_change_id(change_id: str) -> str:
313
+ """
314
+ Sanitize change_id to prevent path traversal attacks.
315
+ Only allows alphanumeric characters, hyphens, underscores, and dots.
316
+ Rejects any path separators or parent directory references.
317
+ """
318
+ if not change_id or not isinstance(change_id, str):
319
+ raise ValueError("change_id must be a non-empty string")
320
+ # Strip whitespace
321
+ sanitized = change_id.strip()
322
+ # Reject path separators and parent directory traversal
323
+ if any(c in sanitized for c in ('/', '\\', '..')):
324
+ raise ValueError(
325
+ f"Invalid change_id '{change_id}': must not contain path separators or '..'"
326
+ )
327
+ # Only allow safe characters: alphanumeric, hyphen, underscore, dot
328
+ if not re.match(r'^[a-zA-Z0-9_\-\.]+$', sanitized):
329
+ raise ValueError(
330
+ f"Invalid change_id '{change_id}': only alphanumeric, hyphen, underscore, and dot allowed"
331
+ )
332
+ return sanitized
333
+
334
+ def _safe_pending_path(self, change_id: str, suffix: str = ".pending.json") -> Path:
335
+ """Build a pending-file path and verify it stays inside pending_dir."""
336
+ safe_id = self._sanitize_change_id(change_id)
337
+ target = (self.pending_dir / f"{safe_id}{suffix}").resolve()
338
+ if not str(target).startswith(str(self.pending_dir.resolve())):
339
+ raise ValueError(f"Path traversal blocked for change_id '{change_id}'")
340
+ return target
341
+
311
342
  def propose_change(self, change_id: str, key: str, value: Any,
312
343
  source_agent: str = "unknown", ttl: Optional[int] = None,
313
344
  operation: str = "write") -> dict[str, Any]:
@@ -317,7 +348,7 @@ Last Updated: {datetime.now(timezone.utc).isoformat()}
317
348
  The change is written to a .pending file and must be validated
318
349
  and committed by the orchestrator before it takes effect.
319
350
  """
320
- pending_file = self.pending_dir / f"{change_id}.pending.json"
351
+ pending_file = self._safe_pending_path(change_id)
321
352
 
322
353
  # Check for duplicate change_id
323
354
  if pending_file.exists():
@@ -365,7 +396,7 @@ Last Updated: {datetime.now(timezone.utc).isoformat()}
365
396
  - No conflicting changes to the same key
366
397
  - Base hash matches (data hasn't changed since proposal)
367
398
  """
368
- pending_file = self.pending_dir / f"{change_id}.pending.json"
399
+ pending_file = self._safe_pending_path(change_id)
369
400
 
370
401
  if not pending_file.exists():
371
402
  return {
@@ -439,7 +470,7 @@ Last Updated: {datetime.now(timezone.utc).isoformat()}
439
470
  """
440
471
  Apply a validated change atomically (Step 3 of atomic commit).
441
472
  """
442
- pending_file = self.pending_dir / f"{change_id}.pending.json"
473
+ pending_file = self._safe_pending_path(change_id)
443
474
 
444
475
  if not pending_file.exists():
445
476
  return {
@@ -479,9 +510,10 @@ Last Updated: {datetime.now(timezone.utc).isoformat()}
479
510
  change_set["status"] = "committed"
480
511
  change_set["committed_at"] = datetime.now(timezone.utc).isoformat()
481
512
 
513
+ safe_id = self._sanitize_change_id(change_id)
482
514
  archive_dir = self.pending_dir / "archive"
483
515
  archive_dir.mkdir(exist_ok=True)
484
- archive_file = archive_dir / f"{change_id}.committed.json"
516
+ archive_file = archive_dir / f"{safe_id}.committed.json"
485
517
  archive_file.write_text(json.dumps(change_set, indent=2))
486
518
 
487
519
  # Remove pending file
@@ -497,7 +529,7 @@ Last Updated: {datetime.now(timezone.utc).isoformat()}
497
529
 
498
530
  def abort_change(self, change_id: str) -> dict[str, Any]:
499
531
  """Abort a pending change without applying it."""
500
- pending_file = self.pending_dir / f"{change_id}.pending.json"
532
+ pending_file = self._safe_pending_path(change_id)
501
533
 
502
534
  if not pending_file.exists():
503
535
  return {
@@ -510,9 +542,10 @@ Last Updated: {datetime.now(timezone.utc).isoformat()}
510
542
  change_set["aborted_at"] = datetime.now(timezone.utc).isoformat()
511
543
 
512
544
  # Archive the aborted change
545
+ safe_id = self._sanitize_change_id(change_id)
513
546
  archive_dir = self.pending_dir / "archive"
514
547
  archive_dir.mkdir(exist_ok=True)
515
- archive_file = archive_dir / f"{change_id}.aborted.json"
548
+ archive_file = archive_dir / f"{safe_id}.aborted.json"
516
549
  archive_file.write_text(json.dumps(change_set, indent=2))
517
550
 
518
551
  pending_file.unlink()