network-ai 5.3.0 → 5.3.1

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/QUICKSTART.md CHANGED
@@ -18,7 +18,7 @@ npm install
18
18
  npx ts-node setup.ts --check
19
19
  ```
20
20
 
21
- **Zero external AI dependencies.** All 28 adapters are self-contained — add framework SDKs only when you need them.
21
+ **Zero external AI dependencies.** All 29 adapters are self-contained — add framework SDKs only when you need them.
22
22
 
23
23
  ---
24
24
 
@@ -254,7 +254,7 @@ export class MyFrameworkAdapter extends BaseAdapter {
254
254
  ```bash
255
255
  npx ts-node test-standalone.ts # 88 core tests
256
256
  npx ts-node test-security.ts # 34 security tests
257
- npx ts-node test-adapters.ts # 218 adapter tests (all 28 frameworks)
257
+ npx ts-node test-adapters.ts # 218 adapter tests (all 29 frameworks)
258
258
  npx ts-node test-cli.ts # 65 CLI tests
259
259
  npx ts-node test-qa.ts # 67 QA orchestrator tests
260
260
  ```
@@ -265,7 +265,7 @@ npx ts-node test-qa.ts # 67 QA orchestrator tests
265
265
 
266
266
  ```bash
267
267
  npx ts-node setup.ts --check # Verify installation
268
- npx ts-node setup.ts --list # List all 28 adapters
268
+ npx ts-node setup.ts --list # List all 29 adapters
269
269
  npx ts-node setup.ts --example # Generate example.ts
270
270
  ```
271
271
 
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Website](https://img.shields.io/badge/website-network--ai.org-4b9df2?style=flat&logo=web&logoColor=white)](https://network-ai.org/)
6
6
  [![CI](https://github.com/Jovancoding/Network-AI/actions/workflows/ci.yml/badge.svg)](https://github.com/Jovancoding/Network-AI/actions/workflows/ci.yml)
7
7
  [![CodeQL](https://github.com/Jovancoding/Network-AI/actions/workflows/codeql.yml/badge.svg)](https://github.com/Jovancoding/Network-AI/actions/workflows/codeql.yml)
8
- [![Release](https://img.shields.io/badge/release-v5.3.0-blue.svg)](https://github.com/Jovancoding/Network-AI/releases)
8
+ [![Release](https://img.shields.io/badge/release-v5.3.1-blue.svg)](https://github.com/Jovancoding/Network-AI/releases)
9
9
  [![npm](https://img.shields.io/npm/dw/network-ai.svg?label=npm%20downloads)](https://www.npmjs.com/package/network-ai)
10
10
  [![Tests](https://img.shields.io/badge/tests-2899%20passing-brightgreen.svg)](#testing)
11
11
  [![Adapters](https://img.shields.io/badge/frameworks-29%20supported-blueviolet.svg)](#adapter-system)
package/SKILL.md CHANGED
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  name: network-ai
3
- description: "Local Python orchestration skill: multi-agent workflows via shared blackboard file, permission gating, token budget scripts, and persistent project context. The bundled Python scripts make no network calls and have zero third-party dependencies. Workflow delegations via the host platform's sessions_send may invoke external model APIs."
3
+ description: "Local Python orchestration skill: multi-agent workflows via shared blackboard file, permission gating, token budget scripts, and persistent project context. All bundled scripts run locally with zero network calls and zero third-party dependencies."
4
4
  metadata:
5
5
  openclaw:
6
6
  emoji: "\U0001F41D"
7
7
  homepage: https://network-ai.org
8
8
  bundle_scope: "Python scripts only (scripts/*.py). All execution is local. Only Python stdlib — no other runtimes, adapters, or CLI tools are included."
9
- network_calls: "none — bundled scripts make zero network calls. The host platform's sessions_send (not part of this skill) may invoke external models."
10
- sessions_send: "NOT implemented or invoked by this skill. sessions_send is a host-platform built-in. This skill only provides budget guards that run before the platform delegates."
9
+ network_calls: "none — bundled scripts make zero network calls and spawn no subprocesses."
10
+ inter_agent_comms: "none this skill does not implement, invoke, or control inter-agent messaging or sessions_send. All coordination is via local file-based blackboard only."
11
+ sessions_send: "NOT implemented or invoked by this skill. sessions_send is a host-platform built-in entirely outside this skill's control. See data-flow notice below."
11
12
  sessions_ops: "platform-provided — outside this skill's control"
12
13
  requires:
13
14
  bins:
@@ -31,7 +32,11 @@ metadata:
31
32
 
32
33
  > **Scope:** The bundled Python scripts (`scripts/*.py`) make **no network calls**, use only the Python standard library, and have **zero third-party dependencies**. Tokens are UUID-based (`grant_{uuid4().hex}`) stored in `data/active_grants.json`. Audit logging is plain JSONL (`data/audit_log.jsonl`).
33
34
 
34
- > **Data-flow notice:** This skill does NOT implement, invoke, or control `sessions_send`. That is a host-platform built-in (OpenClaw runtime). The orchestration instructions below describe *when* to call the platform's `sessions_send` after budget checks pass but the actual network call, model endpoint, and data transmission are entirely the host platform's responsibility. If you need to prevent external network calls, disable or reroute `sessions_send` in your platform settings before installing this skill.
35
+ > **Advisory tokens notice:** Grant tokens issued by `check_permission.py` are **advisory scoring outputs only** the caller-supplied `--agent` identity is not cryptographically verified. Downstream systems must not treat these tokens as authenticated credentials without adding a separate identity-verification step or human approval gate, especially for PAYMENTS, DATABASE, and FILE_EXPORT resources.
36
+
37
+ > **Data-flow notice (host platform — not this skill):** This skill does NOT implement, invoke, or control `sessions_send`. That is a host-platform built-in (OpenClaw runtime). The orchestration instructions below describe *when* to call the platform’s `sessions_send` after budget checks pass — but the actual network call, model endpoint, and data transmission are entirely the **host platform’s** responsibility. If you need to prevent external network calls, disable or reroute `sessions_send` in your **platform settings** before installing this skill. This skill has no access to that configuration.
38
+
39
+ > **Context file integrity:** The `context_manager.py inject` command now validates `data/project-context.json` for injection patterns and oversized fields before printing the context block. Review any warnings printed to stderr before passing the output to an agent system prompt.
35
40
 
36
41
  > **PII / sensitive-data warning:** The `justification` field in permission requests and the audit log (`data/audit_log.jsonl`) store free-text strings provided by agents. **Do not include PII, secrets, or credentials in justification text.** Consider restricting file permissions on `data/` or running this skill in an isolated workspace.
37
42
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "network-ai",
3
- "version": "5.3.0",
3
+ "version": "5.3.1",
4
4
  "description": "AI agent orchestration framework for TypeScript/Node.js - 29 adapters (LangChain, AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, OpenClaw, A2A, Codex, MiniMax, NemoClaw, APS, Copilot, LangGraph, Anthropic Computer Use, OpenAI Agents SDK, Vertex AI, Pydantic AI, Browser Agent, Hermes, Orchestrator, RLM + streaming variants). Built-in CLI, security, swarm intelligence, real-time streaming, and agentic workflow patterns.",
5
5
  "homepage": "https://network-ai.org",
6
6
  "main": "dist/index.js",
@@ -41,6 +41,17 @@ GRANT_TOKEN_TTL_MINUTES = 5
41
41
  GRANTS_FILE = Path(__file__).parent.parent / "data" / "active_grants.json"
42
42
  AUDIT_LOG = Path(__file__).parent.parent / "data" / "audit_log.jsonl"
43
43
 
44
+ # ADVISORY TOKENS — IMPORTANT
45
+ # Grant tokens produced by this script are ADVISORY scoring outputs only.
46
+ # They are NOT authenticated credentials. The agent_id supplied via --agent
47
+ # is accepted as-is from the caller and is NOT verified against an identity
48
+ # provider. Downstream systems MUST treat these tokens as hints, not proof
49
+ # of identity, and SHOULD require a separate authenticated session or human
50
+ # approval before honouring access to sensitive resources.
51
+ #
52
+ # For PAYMENTS, DATABASE (write), and FILE_EXPORT the caller must also pass
53
+ # --confirm-high-risk to acknowledge the advisory-only nature of the grant.
54
+
44
55
  # Default trust levels for known agents
45
56
  DEFAULT_TRUST_LEVELS = {
46
57
  "orchestrator": 0.9,
@@ -49,6 +60,13 @@ DEFAULT_TRUST_LEVELS = {
49
60
  "risk_assessor": 0.85,
50
61
  }
51
62
 
63
+ # Agents whose identity is pre-registered. Any agent_id NOT in this set
64
+ # receives a reduced trust score (0.3) and triggers an advisory warning.
65
+ KNOWN_AGENTS: set[str] = set(DEFAULT_TRUST_LEVELS.keys())
66
+
67
+ # Resource types that require --confirm-high-risk before a grant is issued
68
+ HIGH_RISK_RESOURCES: set[str] = {"PAYMENTS", "DATABASE"}
69
+
52
70
  # Base risk scores for resource types
53
71
  BASE_RISKS = {
54
72
  "DATABASE": 0.5, # Internal database access
@@ -258,48 +276,88 @@ def save_grant(grant: dict[str, Any]) -> None:
258
276
  GRANTS_FILE.write_text(json.dumps(grants, indent=2))
259
277
 
260
278
 
261
- def evaluate_permission(agent_id: str, resource_type: str,
262
- justification: str, scope: Optional[str] = None) -> dict[str, Any]:
279
+ def evaluate_permission(
280
+ agent_id: str,
281
+ resource_type: str,
282
+ justification: str,
283
+ scope: Optional[str] = None,
284
+ confirm_high_risk: bool = False,
285
+ ) -> dict[str, Any]:
263
286
  """
264
287
  Evaluate a permission request using weighted scoring.
265
-
288
+
289
+ NOTE: These tokens are ADVISORY ONLY — the caller supplies their own
290
+ agent_id and it is not authenticated. Downstream systems must not treat
291
+ the resulting token as proof of identity without additional verification.
292
+
266
293
  Weights:
267
294
  - Justification Quality: 40%
268
295
  - Agent Trust Level: 30%
269
296
  - Risk Assessment: 30%
270
297
  """
298
+ # Warn if agent_id is not in the pre-registered known-agents list
299
+ unknown_agent = agent_id not in KNOWN_AGENTS
300
+
271
301
  # Log the request
272
302
  log_audit("permission_request", {
273
303
  "agent_id": agent_id,
274
304
  "resource_type": resource_type,
275
305
  "justification": justification,
276
- "scope": scope
306
+ "scope": scope,
307
+ "unknown_agent": unknown_agent,
277
308
  })
278
-
309
+
310
+ # Require explicit acknowledgement for high-risk resources
311
+ needs_confirmation = (
312
+ resource_type in HIGH_RISK_RESOURCES
313
+ or (resource_type == "DATABASE" and scope and re.search(
314
+ r'\b(write|delete|update|modify|create)\b', scope, re.IGNORECASE
315
+ ))
316
+ )
317
+ if needs_confirmation and not confirm_high_risk:
318
+ return {
319
+ "granted": False,
320
+ "advisory": True,
321
+ "reason": (
322
+ f"{resource_type} is a high-risk resource. Re-run with --confirm-high-risk "
323
+ "to acknowledge that this token is advisory only and does not authenticate "
324
+ "the supplied agent identity."
325
+ ),
326
+ "scores": {"justification": None, "trust": None, "risk": None},
327
+ }
328
+
279
329
  # 1. Justification Quality (40% weight)
280
330
  justification_score = score_justification(justification)
281
331
  if justification_score < 0.3:
282
332
  return {
283
333
  "granted": False,
334
+ "advisory": True,
284
335
  "reason": "Justification is insufficient. Please provide specific task context.",
285
336
  "scores": {
286
337
  "justification": justification_score,
287
338
  "trust": None,
288
- "risk": None
289
- }
339
+ "risk": None,
340
+ },
290
341
  }
291
-
342
+
292
343
  # 2. Agent Trust Level (30% weight)
293
- trust_level = DEFAULT_TRUST_LEVELS.get(agent_id, 0.5)
344
+ # Unknown agents receive a reduced base trust score (0.3) — their identity
345
+ # has not been pre-registered and cannot be verified by this script.
346
+ trust_level = DEFAULT_TRUST_LEVELS.get(agent_id, 0.3) if not unknown_agent \
347
+ else 0.3
294
348
  if trust_level < 0.4:
295
349
  return {
296
350
  "granted": False,
297
- "reason": "Agent trust level is below threshold. Escalate to human operator.",
351
+ "advisory": True,
352
+ "reason": (
353
+ "Agent trust level is below threshold. Escalate to human operator."
354
+ + (" (unrecognized agent_id — identity not pre-registered)" if unknown_agent else "")
355
+ ),
298
356
  "scores": {
299
357
  "justification": justification_score,
300
358
  "trust": trust_level,
301
- "risk": None
302
- }
359
+ "risk": None,
360
+ },
303
361
  }
304
362
 
305
363
  # 3. Risk Assessment (30% weight)
@@ -307,12 +365,13 @@ def evaluate_permission(agent_id: str, resource_type: str,
307
365
  if risk_score > 0.8:
308
366
  return {
309
367
  "granted": False,
368
+ "advisory": True,
310
369
  "reason": "Risk assessment exceeds acceptable threshold. Narrow the requested scope.",
311
370
  "scores": {
312
371
  "justification": justification_score,
313
372
  "trust": trust_level,
314
- "risk": risk_score
315
- }
373
+ "risk": risk_score,
374
+ },
316
375
  }
317
376
 
318
377
  # Calculate weighted approval score
@@ -325,13 +384,14 @@ def evaluate_permission(agent_id: str, resource_type: str,
325
384
  if weighted_score < 0.5:
326
385
  return {
327
386
  "granted": False,
387
+ "advisory": True,
328
388
  "reason": f"Combined evaluation score ({weighted_score:.2f}) below threshold (0.5).",
329
389
  "scores": {
330
390
  "justification": justification_score,
331
391
  "trust": trust_level,
332
392
  "risk": risk_score,
333
- "weighted": weighted_score
334
- }
393
+ "weighted": weighted_score,
394
+ },
335
395
  }
336
396
 
337
397
  # Generate grant
@@ -346,15 +406,19 @@ def evaluate_permission(agent_id: str, resource_type: str,
346
406
  "scope": scope,
347
407
  "expires_at": expires_at,
348
408
  "restrictions": restrictions,
349
- "granted_at": datetime.now(timezone.utc).isoformat()
409
+ "granted_at": datetime.now(timezone.utc).isoformat(),
410
+ "advisory": True, # Always advisory — not an authenticated credential
411
+ "unknown_agent": unknown_agent,
350
412
  }
351
-
413
+
352
414
  # Save grant and log
353
415
  save_grant(grant)
354
416
  log_audit("permission_granted", grant)
355
-
417
+
356
418
  return {
357
419
  "granted": True,
420
+ "advisory": True, # Token is advisory — agent identity was not verified
421
+ "unknown_agent": unknown_agent,
358
422
  "token": token,
359
423
  "expires_at": expires_at,
360
424
  "restrictions": restrictions,
@@ -362,8 +426,14 @@ def evaluate_permission(agent_id: str, resource_type: str,
362
426
  "justification": justification_score,
363
427
  "trust": trust_level,
364
428
  "risk": risk_score,
365
- "weighted": weighted_score
366
- }
429
+ "weighted": weighted_score,
430
+ },
431
+ "notice": (
432
+ "This token was issued based on local scoring only. "
433
+ "The agent identity supplied via --agent was NOT cryptographically verified. "
434
+ "Treat this token as advisory and require human approval before granting "
435
+ "access to sensitive systems."
436
+ ),
367
437
  }
368
438
 
369
439
 
@@ -649,6 +719,16 @@ Examples:
649
719
  action="store_true",
650
720
  help="Output result as JSON"
651
721
  )
722
+ parser.add_argument(
723
+ "--confirm-high-risk",
724
+ action="store_true",
725
+ dest="confirm_high_risk",
726
+ help=(
727
+ "Required for PAYMENTS and DATABASE resources. Acknowledges that the issued "
728
+ "token is advisory only and that the caller-supplied agent identity was not "
729
+ "cryptographically verified."
730
+ ),
731
+ )
652
732
 
653
733
  args = parser.parse_args()
654
734
 
@@ -672,17 +752,21 @@ Examples:
672
752
  agent_id=args.agent,
673
753
  resource_type=args.resource,
674
754
  justification=args.justification,
675
- scope=args.scope
755
+ scope=args.scope,
756
+ confirm_high_risk=getattr(args, "confirm_high_risk", False),
676
757
  )
677
758
 
678
759
  if args.json:
679
760
  print(json.dumps(result, indent=2))
680
761
  else:
681
762
  if result["granted"]:
682
- print("GRANTED")
683
- print(f"Token: {result['token']}")
684
- print(f"Expires: {result['expires_at']}")
763
+ print("GRANTED [ADVISORY — agent identity was NOT verified]")
764
+ print(f"Token: {result['token']}")
765
+ print(f"Expires: {result['expires_at']}")
685
766
  print(f"Restrictions: {', '.join(result['restrictions'])}")
767
+ if result.get("unknown_agent"):
768
+ print("WARNING: agent_id is not in the pre-registered known-agents list.")
769
+ print(f"\nNOTICE: {result.get('notice', '')}")
686
770
  else:
687
771
  print("DENIED")
688
772
  print(f"Reason: {result['reason']}")
@@ -74,6 +74,72 @@ def _now_iso() -> str:
74
74
  return datetime.now(timezone.utc).isoformat()
75
75
 
76
76
 
77
+ def _validate_context(ctx: dict[str, Any]) -> list[str]:
78
+ """
79
+ Validate the project context file against the expected schema.
80
+
81
+ Returns a list of warning strings (empty = clean).
82
+ Checks:
83
+ - Required top-level keys are present
84
+ - String fields are not excessively long (injection/poisoning guard)
85
+ - List entries are strings or dicts, not executable-looking content
86
+ - No obvious prompt-injection patterns in goals, decisions, or banned entries
87
+ """
88
+ import re as _re
89
+ warnings: list[str] = []
90
+
91
+ REQUIRED_KEYS = {"project", "goals", "stack", "milestones", "decisions",
92
+ "banned_approaches", "updated_at"}
93
+ missing = REQUIRED_KEYS - set(ctx.keys())
94
+ if missing:
95
+ warnings.append(f"Missing keys in context file: {', '.join(sorted(missing))}")
96
+
97
+ # Field length caps
98
+ project = ctx.get("project", {})
99
+ for field in ("name", "description", "version"):
100
+ val = project.get(field, "")
101
+ if isinstance(val, str) and len(val) > 500:
102
+ warnings.append(f"project.{field} exceeds 500 characters \u2014 consider shortening.")
103
+
104
+ # Injection pattern check on free-text list fields
105
+ INJECTION_RE = _re.compile(
106
+ r'ignore\s+(previous|above|prior|all)|override\s+(policy|restriction|rule)|'
107
+ r'system\s*prompt|you\s+are\s+(now|a)|act\s+as\s+(if|a|an)|'
108
+ r'pretend\s+(to|that|you)|bypass\s+(security|check|restriction)|'
109
+ r'disregard\s+(policy|rule)|admin\s+(mode|access|override)|'
110
+ r'\bsudo\b|\bjailbreak\b',
111
+ _re.IGNORECASE,
112
+ )
113
+
114
+ def _check_text(label: str, text: str) -> None:
115
+ if INJECTION_RE.search(text):
116
+ warnings.append(
117
+ f"Possible injection pattern detected in {label}: {text[:80]!r}"
118
+ )
119
+ if len(text) > 2000:
120
+ warnings.append(f"{label} entry exceeds 2000 characters \u2014 review before injecting.")
121
+
122
+ for i, goal in enumerate(ctx.get("goals", [])):
123
+ if isinstance(goal, str):
124
+ _check_text(f"goals[{i}]", goal)
125
+
126
+ for i, dec in enumerate(ctx.get("decisions", [])):
127
+ if isinstance(dec, dict):
128
+ dec_dict = cast(dict[str, object], dec)
129
+ for fld in ("decision", "rationale"):
130
+ fld_val = dec_dict.get(fld)
131
+ if isinstance(fld_val, str):
132
+ _check_text(f"decisions[{i}].{fld}", fld_val)
133
+ elif isinstance(dec, str):
134
+ _check_text(f"decisions[{i}]", dec)
135
+
136
+ for i, banned in enumerate(ctx.get("banned_approaches", [])):
137
+ if isinstance(banned, str):
138
+ _check_text(f"banned_approaches[{i}]", banned)
139
+
140
+ return warnings
141
+
142
+
77
143
  def _load() -> dict[str, Any]:
78
144
  if not CONTEXT_PATH.exists():
79
145
  print(
@@ -127,6 +193,11 @@ def cmd_init(args: argparse.Namespace) -> int:
127
193
 
128
194
  def cmd_show(args: argparse.Namespace) -> int: # noqa: ARG001
129
195
  ctx = _load()
196
+ warnings = _validate_context(ctx)
197
+ if warnings:
198
+ print("[context_manager] VALIDATION WARNINGS — review before injecting:", file=sys.stderr)
199
+ for w in warnings:
200
+ print(f" ! {w}", file=sys.stderr)
130
201
  print(json.dumps(ctx, indent=2))
131
202
  return 0
132
203
 
@@ -134,6 +205,12 @@ def cmd_show(args: argparse.Namespace) -> int: # noqa: ARG001
134
205
  def cmd_inject(args: argparse.Namespace) -> int: # noqa: ARG001
135
206
  """Print a formatted block suitable for injection into an agent system prompt."""
136
207
  ctx = _load()
208
+ warnings = _validate_context(ctx)
209
+ if warnings:
210
+ print("[context_manager] VALIDATION WARNINGS \u2014 context has potential issues:", file=sys.stderr)
211
+ for w in warnings:
212
+ print(f" ! {w}", file=sys.stderr)
213
+ print("[context_manager] Proceeding with inject, but review warnings above.", file=sys.stderr)
137
214
  p = ctx.get("project", {})
138
215
 
139
216
  lines: list[str] = []