network-ai 5.3.1 → 5.4.0
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/INTEGRATION_GUIDE.md +80 -3
- package/QUICKSTART.md +40 -0
- package/README.md +8 -7
- package/SKILL.md +41 -64
- package/bin/cli.ts +155 -0
- package/dist/bin/cli.js +156 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-runtime.d.ts +28 -0
- package/dist/lib/agent-runtime.d.ts.map +1 -1
- package/dist/lib/agent-runtime.js +68 -1
- package/dist/lib/agent-runtime.js.map +1 -1
- package/dist/lib/env-manager.d.ts +179 -0
- package/dist/lib/env-manager.d.ts.map +1 -0
- package/dist/lib/env-manager.js +491 -0
- package/dist/lib/env-manager.js.map +1 -0
- package/dist/lib/locked-blackboard.d.ts +6 -0
- package/dist/lib/locked-blackboard.d.ts.map +1 -1
- package/dist/lib/locked-blackboard.js +24 -5
- package/dist/lib/locked-blackboard.js.map +1 -1
- package/dist/lib/mcp-tool-consumer.js +1 -1
- package/package.json +1 -1
- package/scripts/blackboard.py +25 -4
- package/scripts/check_permission.py +28 -2
- package/scripts/context_manager.py +28 -2
- package/scripts/swarm_guard.py +25 -2
- package/scripts/validate_token.py +23 -1
package/INTEGRATION_GUIDE.md
CHANGED
|
@@ -305,6 +305,83 @@ Connect your AI model to `http://localhost:3001/sse` — it can now:
|
|
|
305
305
|
|
|
306
306
|
---
|
|
307
307
|
|
|
308
|
+
### Phase 7 — Multi-Environment Promotion (v5.4.0+)
|
|
309
|
+
|
|
310
|
+
**Goal:** Promote validated config from dev → staging → production through gate-enforced checkpoints.
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
# Initialise the full environment chain in one command
|
|
314
|
+
npx network-ai env init --all
|
|
315
|
+
|
|
316
|
+
# After validating in dev, promote config to st (auto-gate — no approval needed)
|
|
317
|
+
npx network-ai env promote --from dev --to st
|
|
318
|
+
|
|
319
|
+
# Review differences before promoting further
|
|
320
|
+
npx network-ai env diff --from st --to sit
|
|
321
|
+
|
|
322
|
+
# preprod requires a human confirmation
|
|
323
|
+
npx network-ai env promote --from sit --to qa
|
|
324
|
+
npx network-ai env promote --from qa --to preprod --confirmed-by "jane.doe"
|
|
325
|
+
|
|
326
|
+
# prod requires an approval token
|
|
327
|
+
npx network-ai env promote --from preprod --to prod --approved-by "security-board"
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Backup and rollback:**
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
# Create a named backup before a risky change
|
|
334
|
+
npx network-ai env backup create --env prod
|
|
335
|
+
|
|
336
|
+
# List available backups
|
|
337
|
+
npx network-ai env backup list --env prod
|
|
338
|
+
|
|
339
|
+
# Roll back to the latest backup
|
|
340
|
+
npx network-ai env backup restore --env prod --latest
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**From TypeScript:**
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
import { EnvironmentManager } from 'network-ai';
|
|
347
|
+
|
|
348
|
+
const mgr = new EnvironmentManager('.');
|
|
349
|
+
await mgr.initAll();
|
|
350
|
+
|
|
351
|
+
// Promote config files only (live state never promotes)
|
|
352
|
+
const result = await mgr.promote('dev', 'st');
|
|
353
|
+
console.log(result.copiedFiles); // ['trust_levels.json', 'budget_ceilings.json']
|
|
354
|
+
|
|
355
|
+
// Auto-backup destination before overwriting
|
|
356
|
+
const backup = await mgr.backup('st');
|
|
357
|
+
console.log(backup.id); // '2026-05-10T12-00-00-000Z'
|
|
358
|
+
|
|
359
|
+
// Diff two envs
|
|
360
|
+
const diff = await mgr.diff('dev', 'prod');
|
|
361
|
+
for (const f of diff.files) {
|
|
362
|
+
console.log(f.file, f.status, f.changedKeys);
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Source protection** — lock agents inside `data/<env>/`:
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { AgentRuntime, SandboxPolicy } from 'network-ai';
|
|
370
|
+
|
|
371
|
+
const policy: SandboxPolicy = {
|
|
372
|
+
allowedCommands: [],
|
|
373
|
+
allowedPaths: ['data/dev/'],
|
|
374
|
+
sourceProtection: true,
|
|
375
|
+
env: 'dev',
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const runtime = new AgentRuntime({ policy });
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Any `FileAccessor.read/write/list` call outside `data/dev/` will throw `SourceProtectionError` and be caught as `{ success: false, error: '...' }`.
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
308
385
|
## 5. Enterprise Concerns
|
|
309
386
|
|
|
310
387
|
### Authentication & IAM
|
|
@@ -419,7 +496,7 @@ Run these before declaring the integration production-ready:
|
|
|
419
496
|
- [ ] `npx ts-node test-phase4.ts` — 147 behavioral tests pass
|
|
420
497
|
- [ ] `npx ts-node test-qa.ts` — 67 QA orchestrator tests pass
|
|
421
498
|
- [ ] `npx ts-node test-phase7.ts` — 94 Phase 7 tests pass (hooks, flow control, composer, semantic search)
|
|
422
|
-
- [ ] `npm run test:all` — all 2,
|
|
499
|
+
- [ ] `npm run test:all` — all 2,976 tests pass across 29 suites
|
|
423
500
|
- [ ] `npm run demo -- --08` runs to completion in < 10 seconds
|
|
424
501
|
|
|
425
502
|
### Race Condition Safety
|
|
@@ -477,7 +554,7 @@ Run these before declaring the integration production-ready:
|
|
|
477
554
|
|----------|---------------|
|
|
478
555
|
| [QUICKSTART.md](QUICKSTART.md) | Get running in 5 minutes |
|
|
479
556
|
| [QUICKSTART.md § CLI](QUICKSTART.md) | CLI reference — bb, auth, budget, audit commands |
|
|
480
|
-
| [references/adapter-system.md](references/adapter-system.md) | All
|
|
557
|
+
| [references/adapter-system.md](references/adapter-system.md) | All 29 adapters with code examples |
|
|
481
558
|
| [references/trust-levels.md](references/trust-levels.md) | Trust scoring formula and agent roles |
|
|
482
559
|
| [references/auth-guardian.md](references/auth-guardian.md) | Permission system, justification scoring, token lifecycle |
|
|
483
560
|
| [references/blackboard-schema.md](references/blackboard-schema.md) | Blackboard key conventions and namespacing |
|
|
@@ -487,4 +564,4 @@ Run these before declaring the integration production-ready:
|
|
|
487
564
|
|
|
488
565
|
---
|
|
489
566
|
|
|
490
|
-
*Network-AI v5.
|
|
567
|
+
*Network-AI v5.4.0 · MIT License · https://github.com/Jovancoding/Network-AI*
|
package/QUICKSTART.md
CHANGED
|
@@ -323,6 +323,46 @@ network-ai auth check grant_a1b2c3...
|
|
|
323
323
|
network-ai auth revoke grant_a1b2c3...
|
|
324
324
|
```
|
|
325
325
|
|
|
326
|
+
### Multi-Environment (`env`) — v5.4.0+
|
|
327
|
+
|
|
328
|
+
Isolate agent state across dev / staging / production using the promotion chain.
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# Initialise all environments at once
|
|
332
|
+
network-ai env init --all
|
|
333
|
+
|
|
334
|
+
# Or initialise a single environment
|
|
335
|
+
network-ai env init --env dev
|
|
336
|
+
|
|
337
|
+
# List environments and key counts
|
|
338
|
+
network-ai env list
|
|
339
|
+
|
|
340
|
+
# Show the promotion chain
|
|
341
|
+
network-ai env chain
|
|
342
|
+
|
|
343
|
+
# Diff two environments (shows +added / -removed / ~changed config keys)
|
|
344
|
+
network-ai env diff --from dev --to prod
|
|
345
|
+
|
|
346
|
+
# Promote config from dev → st (auto-gate, no approval needed)
|
|
347
|
+
network-ai env promote --from dev --to st
|
|
348
|
+
|
|
349
|
+
# Promote to preprod (requires --confirmed-by)
|
|
350
|
+
network-ai env promote --from qa --to preprod --confirmed-by "jane.doe"
|
|
351
|
+
|
|
352
|
+
# Promote to prod (requires --approved-by)
|
|
353
|
+
network-ai env promote --from preprod --to prod --approved-by "security-board"
|
|
354
|
+
|
|
355
|
+
# Backup / restore
|
|
356
|
+
network-ai env backup create --env prod
|
|
357
|
+
network-ai env backup list --env prod
|
|
358
|
+
network-ai env backup restore --env prod --latest
|
|
359
|
+
network-ai env backup prune --env prod --keep 5
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Set `NETWORK_AI_ENV=dev` to automatically route all blackboard and Python script operations to `data/dev/`.
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
326
366
|
### Budget (`budget`)
|
|
327
367
|
|
|
328
368
|
```bash
|
package/README.md
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
[](https://network-ai.org/)
|
|
6
6
|
[](https://github.com/Jovancoding/Network-AI/actions/workflows/ci.yml)
|
|
7
7
|
[](https://github.com/Jovancoding/Network-AI/actions/workflows/codeql.yml)
|
|
8
|
-
[](https://github.com/Jovancoding/Network-AI/releases)
|
|
9
9
|
[](https://www.npmjs.com/package/network-ai)
|
|
10
|
-
[](#testing)
|
|
11
11
|
[](#adapter-system)
|
|
12
12
|
[](LICENSE)
|
|
13
13
|
[](https://socket.dev/npm/package/network-ai/overview)
|
|
@@ -354,7 +354,7 @@ npx ts-node examples/10-nemoclaw-sandbox-swarm.ts
|
|
|
354
354
|
|
|
355
355
|
## Adapter System
|
|
356
356
|
|
|
357
|
-
|
|
357
|
+
29 adapters, zero adapter dependencies. You bring your own SDK objects.
|
|
358
358
|
|
|
359
359
|
| Adapter | Framework / Protocol | Register method |
|
|
360
360
|
|---|---|---|
|
|
@@ -419,7 +419,7 @@ Extend `BaseAdapter` (or `StreamingBaseAdapter` for streaming) to add your own i
|
|
|
419
419
|
npm run test:all # All suites in sequence
|
|
420
420
|
npm test # Core orchestrator
|
|
421
421
|
npm run test:security # Security module
|
|
422
|
-
npm run test:adapters # All
|
|
422
|
+
npm run test:adapters # All 29 adapters
|
|
423
423
|
npm run test:streaming # Streaming adapters
|
|
424
424
|
npm run test:a2a # A2A protocol adapter
|
|
425
425
|
npm run test:codex # Codex adapter
|
|
@@ -429,7 +429,7 @@ npm run test:phase9 # Agent runtime, console, strategy agent
|
|
|
429
429
|
npm run test:phase12 # Context Throttler, Partition Planner, Coverage Gate, Route Classifier
|
|
430
430
|
```
|
|
431
431
|
|
|
432
|
-
**2,
|
|
432
|
+
**2,976 passing assertions across 29 test suites** (`npm run test:all`):
|
|
433
433
|
|
|
434
434
|
| Suite | Assertions | Covers |
|
|
435
435
|
|---|---|---|
|
|
@@ -437,7 +437,7 @@ npm run test:phase12 # Context Throttler, Partition Planner, Coverage Gate,
|
|
|
437
437
|
| `test-phase5f.ts` | 127 | SSE transport, `McpCombinedBridge`, extended MCP tools |
|
|
438
438
|
| `test-phase5g.ts` | 121 | CRDT backend, vector clocks, bidirectional sync |
|
|
439
439
|
| `test-phase6.ts` | 121 | MCP server, control-plane tools, audit tools |
|
|
440
|
-
| `test-adapters.ts` | 218 | All
|
|
440
|
+
| `test-adapters.ts` | 218 | All 29 adapters, registry routing, integration, edge cases |
|
|
441
441
|
| `test-phase5d.ts` | 117 | Pluggable backend (Redis, CRDT, Memory) |
|
|
442
442
|
| `test-standalone.ts` | 88 | Blackboard, auth, integration, persistence, parallelisation, quality gate |
|
|
443
443
|
| `test-phase5e.ts` | 87 | Federated budget tracking |
|
|
@@ -460,6 +460,7 @@ npm run test:phase12 # Context Throttler, Partition Planner, Coverage Gate,
|
|
|
460
460
|
| `test-topology.ts` | 304 | WorkTree, ControlPlane, dashboard server, topology visualization, WebSocket protocol |
|
|
461
461
|
| `test-rlm-phases.ts` | 123 | FederatedBudget child spending, blackboard metadata API, best-partial result, HookContext depth, sub-goal recursion, semaphore fan-out, PhasePipeline compaction, RLMAdapter end-to-end |
|
|
462
462
|
| `test-phase12.ts` | 65 | Context Throttler, Partition Planner, Coverage Gate, Route Classifier, EVALUATING FSM state, runTeam integration |
|
|
463
|
+
| `test-env-manager.ts` | 77 | Multi-environment isolation, promotion chain, backup/restore, source protection, NETWORK_AI_ENV, blackboard env routing |
|
|
463
464
|
| `test.ts` | 39 | Core orchestrator smoke tests |
|
|
464
465
|
|
|
465
466
|
---
|
|
@@ -476,7 +477,7 @@ npm run test:phase12 # Context Throttler, Partition Planner, Coverage Gate,
|
|
|
476
477
|
| [AUDIT_LOG_SCHEMA.md](AUDIT_LOG_SCHEMA.md) | Audit log field reference, all event types, scoring formula |
|
|
477
478
|
| [ADOPTERS.md](ADOPTERS.md) | Known adopters — open a PR to add yourself |
|
|
478
479
|
| [INTEGRATION_GUIDE.md](INTEGRATION_GUIDE.md) | End-to-end integration walkthrough with v5.0 modules |
|
|
479
|
-
| [references/adapter-system.md](references/adapter-system.md) | Adapter architecture, all
|
|
480
|
+
| [references/adapter-system.md](references/adapter-system.md) | Adapter architecture, all 29 adapters, writing custom adapters |
|
|
480
481
|
| [references/auth-guardian.md](references/auth-guardian.md) | Permission scoring, resource types, IAuthValidator interface |
|
|
481
482
|
| [references/trust-levels.md](references/trust-levels.md) | Trust level configuration, APS delegation-chain mapping |
|
|
482
483
|
|
package/SKILL.md
CHANGED
|
@@ -34,7 +34,7 @@ metadata:
|
|
|
34
34
|
|
|
35
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
36
|
|
|
37
|
-
> **Data-flow notice (host platform — not this skill):** This skill does NOT implement, invoke, or control `sessions_send
|
|
37
|
+
> **Data-flow notice (host platform — not this skill):** This skill does NOT implement, invoke, or control `sessions_send` or any inter-agent messaging. All bundled Python scripts are local-only tools (budget guard, blackboard, permission scorer, context manager). If your platform has a `sessions_send` built-in, whether and how it is used is entirely the **host platform’s** responsibility and is outside this skill’s scope. If you need to prevent external network calls, disable or reroute delegation in your **platform settings** before installing this skill.
|
|
38
38
|
|
|
39
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.
|
|
40
40
|
|
|
@@ -60,6 +60,14 @@ pip install filelock # only needed if you see locking issues on Windows
|
|
|
60
60
|
|
|
61
61
|
The `data/` directory is created automatically on first run. No configuration files, environment variables, or credentials are required.
|
|
62
62
|
|
|
63
|
+
> **Multi-environment support (v5.4.0):** All five Python scripts now read the `NETWORK_AI_ENV` environment variable at startup and accept a `--env <name>` CLI argument. When set, all data paths are routed to `data/<env>/` instead of the root `data/` directory. Use this to isolate dev, staging, and production state.
|
|
64
|
+
>
|
|
65
|
+
> ```bash
|
|
66
|
+
> # Run against the dev environment
|
|
67
|
+
> NETWORK_AI_ENV=dev python3 scripts/blackboard.py list
|
|
68
|
+
> python3 scripts/check_permission.py --active-grants --env dev
|
|
69
|
+
> ```
|
|
70
|
+
|
|
63
71
|
Multi-agent coordination system for complex workflows requiring task delegation, parallel execution, and permission-controlled access to sensitive APIs.
|
|
64
72
|
|
|
65
73
|
## 🎯 Orchestrator System Instructions
|
|
@@ -117,12 +125,12 @@ Sub-Task 3 (RECOMMEND): [strategy_advisor]
|
|
|
117
125
|
- Output: Recommendations with rationale
|
|
118
126
|
```
|
|
119
127
|
|
|
120
|
-
### Budget
|
|
128
|
+
### Budget Check Protocol
|
|
121
129
|
|
|
122
|
-
**
|
|
130
|
+
**Run the budget interceptor before any task delegation:**
|
|
123
131
|
|
|
124
132
|
```bash
|
|
125
|
-
#
|
|
133
|
+
# Run this before delegating to any sub-agent
|
|
126
134
|
python {baseDir}/scripts/swarm_guard.py intercept-handoff \
|
|
127
135
|
--task-id "task_001" \
|
|
128
136
|
--from orchestrator \
|
|
@@ -133,10 +141,10 @@ python {baseDir}/scripts/swarm_guard.py intercept-handoff \
|
|
|
133
141
|
**Decision Logic:**
|
|
134
142
|
```
|
|
135
143
|
IF result.allowed == true:
|
|
136
|
-
→
|
|
144
|
+
→ Budget check passed — proceed with the delegated task
|
|
137
145
|
→ Note tokens_spent and remaining_budget
|
|
138
146
|
ELSE:
|
|
139
|
-
→ STOP
|
|
147
|
+
→ STOP — budget exceeded or handoff limit reached
|
|
140
148
|
→ Report blocked reason to user
|
|
141
149
|
→ Consider: reduce scope or abort task
|
|
142
150
|
```
|
|
@@ -273,11 +281,10 @@ python {baseDir}/scripts/swarm_guard.py budget-init \
|
|
|
273
281
|
--description "Q4 Financial Analysis"
|
|
274
282
|
```
|
|
275
283
|
|
|
276
|
-
### 2.
|
|
284
|
+
### 2. Check Budget Before Task Delegation
|
|
277
285
|
|
|
278
|
-
> **Platform note:** `sessions_list`, `sessions_send`, and `sessions_history` are **OpenClaw host platform built-ins** — they are part of the OpenClaw runtime, not provided or invoked by this skill's Python scripts. This skill only runs local `python scripts/*.py` commands. The guidance below describes how to combine the platform's session tools with this skill's budget guard.
|
|
279
286
|
|
|
280
|
-
|
|
287
|
+
Always run the budget guard before delegating any task:
|
|
281
288
|
|
|
282
289
|
```bash
|
|
283
290
|
# 1. Check budget (this skill's Python script)
|
|
@@ -285,17 +292,8 @@ python {baseDir}/scripts/swarm_guard.py intercept-handoff \
|
|
|
285
292
|
--task-id "task_001" --from orchestrator --to data_analyst \
|
|
286
293
|
--message "Analyze Q4 revenue data"
|
|
287
294
|
|
|
288
|
-
# 2. If allowed,
|
|
289
|
-
#
|
|
290
|
-
# sessions_send → send task to another session
|
|
291
|
-
# sessions_history → check results from delegated work
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
**Example delegation prompt:**
|
|
295
|
-
```
|
|
296
|
-
After running swarm_guard.py intercept-handoff and getting result.allowed == true,
|
|
297
|
-
use the OpenClaw sessions_send platform tool to ask the data_analyst session:
|
|
298
|
-
"Analyze Q4 revenue trends from the SAP export data and summarize key insights"
|
|
295
|
+
# 2. If result.allowed == true, proceed with delegation via your platform's built-in tools.
|
|
296
|
+
# If result.allowed == false, stop — budget exceeded or handoff limit reached.
|
|
299
297
|
```
|
|
300
298
|
|
|
301
299
|
### 3. Check Permission Before API Access
|
|
@@ -330,7 +328,7 @@ python {baseDir}/scripts/blackboard.py list
|
|
|
330
328
|
|
|
331
329
|
## Agent-to-Agent Handoff Protocol
|
|
332
330
|
|
|
333
|
-
When delegating tasks between agents
|
|
331
|
+
When delegating tasks between agents, always run the budget guard first.
|
|
334
332
|
|
|
335
333
|
### Step 1: Initialize Budget & Check Capacity
|
|
336
334
|
```bash
|
|
@@ -343,12 +341,6 @@ python {baseDir}/scripts/swarm_guard.py budget-check --task-id "task_001"
|
|
|
343
341
|
|
|
344
342
|
### Step 2: Identify Target Agent
|
|
345
343
|
|
|
346
|
-
> **Platform note:** `sessions_list` is an **OpenClaw host platform built-in**, not provided by this skill.
|
|
347
|
-
|
|
348
|
-
```
|
|
349
|
-
sessions_list # OpenClaw platform operation — find available agents
|
|
350
|
-
```
|
|
351
|
-
|
|
352
344
|
Common agent types:
|
|
353
345
|
| Agent | Specialty |
|
|
354
346
|
|-------|-----------|
|
|
@@ -357,10 +349,10 @@ Common agent types:
|
|
|
357
349
|
| `risk_assessor` | Risk analysis, compliance checks |
|
|
358
350
|
| `orchestrator` | Coordination, task decomposition |
|
|
359
351
|
|
|
360
|
-
### Step 3:
|
|
352
|
+
### Step 3: Run Budget Guard Before Delegation
|
|
361
353
|
|
|
362
354
|
```bash
|
|
363
|
-
#
|
|
355
|
+
# Check budget AND handoff limits before delegating
|
|
364
356
|
python {baseDir}/scripts/swarm_guard.py intercept-handoff \
|
|
365
357
|
--task-id "task_001" \
|
|
366
358
|
--from orchestrator \
|
|
@@ -369,8 +361,8 @@ python {baseDir}/scripts/swarm_guard.py intercept-handoff \
|
|
|
369
361
|
--artifact # Include if expecting output
|
|
370
362
|
```
|
|
371
363
|
|
|
372
|
-
**If ALLOWED:** Proceed
|
|
373
|
-
**If BLOCKED:** Stop
|
|
364
|
+
**If ALLOWED:** Proceed with delegation via your platform's own tools
|
|
365
|
+
**If BLOCKED:** Stop — budget exceeded or handoff limit reached; do not delegate
|
|
374
366
|
|
|
375
367
|
### Step 4: Construct Handoff Message
|
|
376
368
|
|
|
@@ -380,38 +372,25 @@ Include these fields in your delegation:
|
|
|
380
372
|
- **constraints**: Any limitations or requirements
|
|
381
373
|
- **expectedOutput**: What format/content you need back
|
|
382
374
|
|
|
383
|
-
### Step 5:
|
|
375
|
+
### Step 5: Check Results
|
|
384
376
|
|
|
385
|
-
|
|
377
|
+
After delegation completes, read results from the blackboard:
|
|
386
378
|
|
|
379
|
+
```bash
|
|
380
|
+
python {baseDir}/scripts/blackboard.py read "task:001:data_analyst"
|
|
387
381
|
```
|
|
388
|
-
# OpenClaw platform operation (not this skill):
|
|
389
|
-
sessions_send to data_analyst:
|
|
390
|
-
"[HANDOFF]
|
|
391
|
-
Instruction: Analyze Q4 revenue by product category
|
|
392
|
-
Context: Using SAP export from ./data/q4_export.csv
|
|
393
|
-
Constraints: Focus on top 5 categories only
|
|
394
|
-
Expected Output: JSON summary with category, revenue, growth_pct
|
|
395
|
-
[/HANDOFF]"
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
### Step 6: Check Results
|
|
399
382
|
|
|
400
|
-
|
|
383
|
+
## Permission Scoring
|
|
401
384
|
|
|
402
|
-
|
|
403
|
-
sessions_history data_analyst # OpenClaw platform operation — get the response
|
|
404
|
-
```
|
|
385
|
+
> **Tokens are audit scoring outputs only.** Grant tokens from `check_permission.py` are NOT authenticated credentials and must NOT be used as real access control. They are advisory hints based on a local scoring model. Require a separate authenticated identity and explicit human approval before accessing PAYMENTS, DATABASE, or FILE_EXPORT resources.
|
|
405
386
|
|
|
406
|
-
|
|
387
|
+
**Always score permission before accessing:**
|
|
388
|
+
- `DATABASE` — Internal database / data store (abstract label — no external credentials)
|
|
389
|
+
- `PAYMENTS` — Financial/payment data services (abstract label — requires `--confirm-high-risk`)
|
|
390
|
+
- `EMAIL` — Email sending capability (abstract label)
|
|
391
|
+
- `FILE_EXPORT` — Exporting data to local files (abstract label — requires `--confirm-high-risk`)
|
|
407
392
|
|
|
408
|
-
**
|
|
409
|
-
- `DATABASE` - Internal database / data store access
|
|
410
|
-
- `PAYMENTS` - Financial/payment data services
|
|
411
|
-
- `EMAIL` - Email sending capability
|
|
412
|
-
- `FILE_EXPORT` - Exporting data to local files
|
|
413
|
-
|
|
414
|
-
> **Note**: These are abstract local resource type names used by `check_permission.py`. No external API credentials are required or used — all permission evaluation runs locally.
|
|
393
|
+
> **Note**: These are abstract local resource type names used by `check_permission.py`. No external API credentials are required or used — all evaluation runs locally.
|
|
415
394
|
|
|
416
395
|
### Permission Evaluation Criteria
|
|
417
396
|
|
|
@@ -507,16 +486,14 @@ Sequential processing - output of one feeds into next.
|
|
|
507
486
|
|
|
508
487
|
### Example Parallel Workflow
|
|
509
488
|
|
|
510
|
-
> **Platform note:** `sessions_send` and `sessions_history` are **OpenClaw host platform built-ins**, not provided by this skill. This skill provides only the `swarm_guard.py` budget/handoff check that runs before each delegation.
|
|
511
|
-
|
|
512
489
|
```
|
|
513
|
-
# For each delegation below, first run:
|
|
490
|
+
# For each delegation below, first run the budget guard:
|
|
514
491
|
# python {baseDir}/scripts/swarm_guard.py intercept-handoff --task-id "task_001" --from orchestrator --to <agent> --message "<task>"
|
|
515
|
-
#
|
|
516
|
-
1.
|
|
517
|
-
2.
|
|
518
|
-
3.
|
|
519
|
-
4. Wait for all
|
|
492
|
+
# If result.allowed == true, delegate via your platform's own tools.
|
|
493
|
+
1. Delegate to data_analyst: "Extract key metrics from Q4 data"
|
|
494
|
+
2. Delegate to risk_assessor: "Identify compliance risks in Q4 data"
|
|
495
|
+
3. Delegate to strategy_advisor: "Recommend actions based on Q4 trends"
|
|
496
|
+
4. Wait for all results and read them from the blackboard
|
|
520
497
|
5. Synthesize: Combine metrics + risks + recommendations into executive summary
|
|
521
498
|
```
|
|
522
499
|
|
package/bin/cli.ts
CHANGED
|
@@ -16,6 +16,7 @@ import * as readline from 'readline';
|
|
|
16
16
|
import { LockedBlackboard } from '../lib/locked-blackboard';
|
|
17
17
|
import { AuthGuardian } from '../index';
|
|
18
18
|
import { FederatedBudget } from '../lib/federated-budget';
|
|
19
|
+
import { EnvironmentManager } from '../lib/env-manager';
|
|
19
20
|
|
|
20
21
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
21
22
|
const pkg = (() => {
|
|
@@ -30,6 +31,12 @@ function resolveData(opts: { data?: string }): string {
|
|
|
30
31
|
return path.resolve(opts.data ?? path.join(process.cwd(), 'data'));
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
function resolveEnvData(opts: { data?: string; env?: string }): string {
|
|
35
|
+
const base = resolveData(opts);
|
|
36
|
+
if (opts.env) return path.join(base, opts.env);
|
|
37
|
+
return base;
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
function print(obj: unknown, asJson: boolean): void {
|
|
34
41
|
if (asJson) {
|
|
35
42
|
console.log(JSON.stringify(obj, null, 2));
|
|
@@ -59,6 +66,7 @@ program
|
|
|
59
66
|
.version(pkg.version, '-v, --version')
|
|
60
67
|
.enablePositionalOptions()
|
|
61
68
|
.addOption(new Option('--data <path>', 'path to data directory').default('./data'))
|
|
69
|
+
.addOption(new Option('--env <name>', 'target environment (dev|st|sit|qa|sandbox|preprod|prod)'))
|
|
62
70
|
.addOption(new Option('--json', 'output raw JSON (useful for piping)'));
|
|
63
71
|
|
|
64
72
|
// ── bb (blackboard) ───────────────────────────────────────────────────────────
|
|
@@ -302,6 +310,153 @@ auditCmd.command('clear')
|
|
|
302
310
|
print(g.json ? { cleared: logFile } : `✓ cleared ${logFile}`, g.json);
|
|
303
311
|
});
|
|
304
312
|
|
|
313
|
+
// ── env (environment management) ──────────────────────────────────────────────
|
|
314
|
+
|
|
315
|
+
const envCmd = program.command('env').description('Multi-environment management (isolation, promotion, backup)');
|
|
316
|
+
|
|
317
|
+
envCmd.command('init')
|
|
318
|
+
.description('Scaffold an environment data directory (all 7 envs if no --env given)')
|
|
319
|
+
.action((_opts: Record<string, unknown>, cmd: Command) => {
|
|
320
|
+
const g = cmd.optsWithGlobals<{ data: string; env?: string; json: boolean }>();
|
|
321
|
+
const mgr = new EnvironmentManager(resolveData(g));
|
|
322
|
+
if (g.env) {
|
|
323
|
+
mgr.init(g.env);
|
|
324
|
+
print(g.json ? { initialized: g.env } : `✓ initialized env '${g.env}'`, g.json);
|
|
325
|
+
} else {
|
|
326
|
+
mgr.initAll();
|
|
327
|
+
print(g.json ? { initialized: mgr.getChain().concat(['sandbox']) } : `✓ all environments initialized`, g.json);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
envCmd.command('list')
|
|
332
|
+
.description('List all environments with existence and key count')
|
|
333
|
+
.action((_opts: Record<string, unknown>, cmd: Command) => {
|
|
334
|
+
const g = cmd.optsWithGlobals<{ data: string; env?: string; json: boolean }>();
|
|
335
|
+
const mgr = new EnvironmentManager(resolveData(g));
|
|
336
|
+
const envs = mgr.list();
|
|
337
|
+
if (g.json) {
|
|
338
|
+
print(envs, true);
|
|
339
|
+
} else {
|
|
340
|
+
const lines = envs.map(e => `${e.name.padEnd(10)} ${e.exists ? '✓' : '✗'} (${e.keyCount} keys)`);
|
|
341
|
+
console.log(lines.join('\n'));
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
envCmd.command('chain')
|
|
346
|
+
.description('Show the configured promotion chain')
|
|
347
|
+
.action((_opts: Record<string, unknown>, cmd: Command) => {
|
|
348
|
+
const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
|
|
349
|
+
const mgr = new EnvironmentManager(resolveData(g));
|
|
350
|
+
const chain = mgr.getChain();
|
|
351
|
+
print(g.json ? chain : chain.join(' → '), g.json);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
envCmd.command('diff')
|
|
355
|
+
.description('Compare config artefacts between two environments')
|
|
356
|
+
.requiredOption('--from <env>', 'source environment')
|
|
357
|
+
.requiredOption('--to <env>', 'target environment')
|
|
358
|
+
.action((opts: { from: string; to: string }, cmd: Command) => {
|
|
359
|
+
const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
|
|
360
|
+
const mgr = new EnvironmentManager(resolveData(g));
|
|
361
|
+
const result = mgr.diff(opts.from, opts.to);
|
|
362
|
+
if (g.json) {
|
|
363
|
+
print(result, true);
|
|
364
|
+
} else if (result.differences.length === 0) {
|
|
365
|
+
console.log(`No differences between '${opts.from}' and '${opts.to}'`);
|
|
366
|
+
} else {
|
|
367
|
+
for (const d of result.differences) {
|
|
368
|
+
const sym = d.status === 'added' ? '+' : d.status === 'removed' ? '-' : '~';
|
|
369
|
+
console.log(` ${sym} ${d.file} (${d.status})`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
envCmd.command('promote')
|
|
375
|
+
.description('Promote config artefacts one step up the chain')
|
|
376
|
+
.requiredOption('--from <env>', 'source environment')
|
|
377
|
+
.requiredOption('--to <env>', 'target environment')
|
|
378
|
+
.option('--confirmed-by <name>', 'required for preprod gate')
|
|
379
|
+
.option('--approved-by <name>', 'required for prod gate')
|
|
380
|
+
.action((opts: { from: string; to: string; confirmedBy?: string; approvedBy?: string }, cmd: Command) => {
|
|
381
|
+
const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
|
|
382
|
+
const mgr = new EnvironmentManager(resolveData(g));
|
|
383
|
+
try {
|
|
384
|
+
const result = mgr.promote(opts.from, opts.to, {
|
|
385
|
+
confirmedBy: opts.confirmedBy,
|
|
386
|
+
approvedBy: opts.approvedBy,
|
|
387
|
+
});
|
|
388
|
+
print(g.json ? result : `✓ promoted ${opts.from} → ${opts.to} (${result.configsCopied.length} configs copied)`, g.json);
|
|
389
|
+
} catch (err) {
|
|
390
|
+
die(err instanceof Error ? err.message : String(err));
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// ── env backup subcommand group ───────────────────────────────────────────────
|
|
395
|
+
|
|
396
|
+
const envBackup = envCmd.command('backup').description('Backup management for an environment');
|
|
397
|
+
|
|
398
|
+
envBackup.command('create')
|
|
399
|
+
.description('Create a backup of the environment data directory (alias: network-ai env backup)')
|
|
400
|
+
.action((_opts: Record<string, unknown>, cmd: Command) => {
|
|
401
|
+
const g = cmd.optsWithGlobals<{ data: string; env?: string; json: boolean }>();
|
|
402
|
+
if (!g.env) die('--env <name> is required for backup create');
|
|
403
|
+
const mgr = new EnvironmentManager(resolveData(g));
|
|
404
|
+
const result = mgr.backup(g.env);
|
|
405
|
+
print(g.json ? result : `✓ backup created: ${result.backupId} (${result.filesCount} files)`, g.json);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
envBackup.command('list')
|
|
409
|
+
.description('List available backups for an environment')
|
|
410
|
+
.action((_opts: Record<string, unknown>, cmd: Command) => {
|
|
411
|
+
const g = cmd.optsWithGlobals<{ data: string; env?: string; json: boolean }>();
|
|
412
|
+
if (!g.env) die('--env <name> is required for backup list');
|
|
413
|
+
const mgr = new EnvironmentManager(resolveData(g));
|
|
414
|
+
const backups = mgr.listBackups(g.env);
|
|
415
|
+
if (g.json) {
|
|
416
|
+
print(backups, true);
|
|
417
|
+
} else if (backups.length === 0) {
|
|
418
|
+
console.log('(no backups)');
|
|
419
|
+
} else {
|
|
420
|
+
for (const b of backups) {
|
|
421
|
+
console.log(` ${b.backupId} ${b.timestamp} ${(b.sizeBytes / 1024).toFixed(1)} KB`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
envBackup.command('restore')
|
|
427
|
+
.description('Restore an environment from a backup')
|
|
428
|
+
.option('--backup <id>', 'backup ID to restore')
|
|
429
|
+
.option('--latest', 'restore the most recent backup')
|
|
430
|
+
.action((opts: { backup?: string; latest?: boolean }, cmd: Command) => {
|
|
431
|
+
const g = cmd.optsWithGlobals<{ data: string; env?: string; json: boolean }>();
|
|
432
|
+
if (!g.env) die('--env <name> is required for backup restore');
|
|
433
|
+
if (!opts.backup && !opts.latest) die('provide --backup <id> or --latest');
|
|
434
|
+
const mgr = new EnvironmentManager(resolveData(g));
|
|
435
|
+
let backupId = opts.backup;
|
|
436
|
+
if (opts.latest) {
|
|
437
|
+
const backups = mgr.listBackups(g.env);
|
|
438
|
+
if (backups.length === 0) die(`no backups found for env '${g.env}'`);
|
|
439
|
+
backupId = backups[0].backupId;
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
const result = mgr.restore(g.env, backupId!);
|
|
443
|
+
print(g.json ? result : `✓ restored ${result.filesRestored} files from backup '${result.backupId}'`, g.json);
|
|
444
|
+
} catch (err) {
|
|
445
|
+
die(err instanceof Error ? err.message : String(err));
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
envBackup.command('prune')
|
|
450
|
+
.description('Remove old backups, keeping the N most recent')
|
|
451
|
+
.requiredOption('--keep <n>', 'number of backups to retain', (v) => parseInt(v, 10))
|
|
452
|
+
.action((opts: { keep: number }, cmd: Command) => {
|
|
453
|
+
const g = cmd.optsWithGlobals<{ data: string; env?: string; json: boolean }>();
|
|
454
|
+
if (!g.env) die('--env <name> is required for backup prune');
|
|
455
|
+
const mgr = new EnvironmentManager(resolveData(g));
|
|
456
|
+
const deleted = mgr.pruneBackups(g.env, opts.keep);
|
|
457
|
+
print(g.json ? { deleted } : `✓ pruned ${deleted} backup(s), keeping ${opts.keep}`, g.json);
|
|
458
|
+
});
|
|
459
|
+
|
|
305
460
|
// ── parse ─────────────────────────────────────────────────────────────────────
|
|
306
461
|
|
|
307
462
|
// Auto-detect MCP stdio mode: when stdin is piped (not a TTY) and no
|