network-ai 4.1.0 → 4.3.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/README.md CHANGED
@@ -4,22 +4,23 @@
4
4
 
5
5
  [![CI](https://github.com/jovanSAPFIONEER/Network-AI/actions/workflows/ci.yml/badge.svg)](https://github.com/jovanSAPFIONEER/Network-AI/actions/workflows/ci.yml)
6
6
  [![CodeQL](https://github.com/jovanSAPFIONEER/Network-AI/actions/workflows/codeql.yml/badge.svg)](https://github.com/jovanSAPFIONEER/Network-AI/actions/workflows/codeql.yml)
7
- [![Release](https://img.shields.io/badge/release-v4.1.0-blue.svg)](https://github.com/jovanSAPFIONEER/Network-AI/releases)
7
+ [![Release](https://img.shields.io/badge/release-v4.3.0-blue.svg)](https://github.com/jovanSAPFIONEER/Network-AI/releases)
8
8
  [![npm](https://img.shields.io/npm/dw/network-ai.svg?label=npm%20downloads)](https://www.npmjs.com/package/network-ai)
9
- [![Tests](https://img.shields.io/badge/tests-1283%20passing-brightgreen.svg)](#testing)
10
- [![Adapters](https://img.shields.io/badge/frameworks-13%20supported-blueviolet.svg)](#adapter-system)
9
+ [![Tests](https://img.shields.io/badge/tests-1399%20passing-brightgreen.svg)](#testing)
10
+ [![Adapters](https://img.shields.io/badge/frameworks-14%20supported-blueviolet.svg)](#adapter-system)
11
11
  [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
12
12
  [![Socket](https://socket.dev/api/badge/npm/package/network-ai)](https://socket.dev/npm/package/network-ai/overview)
13
13
  [![Node.js](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)](https://nodejs.org)
14
14
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6.svg)](https://typescriptlang.org)
15
15
  [![ClawHub](https://img.shields.io/badge/ClawHub-network--ai-orange.svg)](https://clawhub.ai/skills/network-ai)
16
16
  [![Integration Guide](https://img.shields.io/badge/docs-integration%20guide-informational.svg)](INTEGRATION_GUIDE.md)
17
+ [![Glama](https://img.shields.io/badge/Glama-listed-8A2BE2.svg)](https://glama.ai/mcp/servers/@jovanSAPFIONEER/network-ai)
17
18
 
18
19
  Network-AI is a TypeScript/Node.js multi-agent orchestrator that adds coordination, guardrails, and governance to any AI agent stack.
19
20
 
20
21
  - **Shared blackboard with locking** — atomic `propose → validate → commit` prevents race conditions and split-brain failures across parallel agents
21
22
  - **Guardrails and budgets** — FSM governance, per-agent token ceilings, HMAC audit trails, and permission gating
22
- - **13 adapters** — LangChain (+ streaming), AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, Custom (+ streaming), OpenClaw, and A2A — no glue code, no lock-in
23
+ - **14 adapters** — LangChain (+ streaming), AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, Custom (+ streaming), OpenClaw, A2A, and Codex — no glue code, no lock-in
23
24
 
24
25
  > **The silent failure mode in multi-agent systems:** parallel agents writing to the same key
25
26
  > use last-write-wins by default — one agent's result silently overwrites another's mid-flight.
@@ -52,7 +53,7 @@ Network-AI is a TypeScript/Node.js multi-agent orchestrator that adds coordinati
52
53
  | Race conditions in parallel agents | Atomic blackboard: `propose → validate → commit` with file-system mutex |
53
54
  | Agent overspend / runaway costs | `FederatedBudget` — hard per-agent token ceilings with live spend tracking |
54
55
  | No visibility into what agents did | HMAC-signed audit log on every write, permission grant, and FSM transition |
55
- | Locked into one AI framework | 13 adapters — mix LangChain + AutoGen + CrewAI + A2A + custom in one swarm |
56
+ | Locked into one AI framework | 14 adapters — mix LangChain + AutoGen + CrewAI + Codex + custom in one swarm |
56
57
  | Agents escalating beyond their scope | `AuthGuardian` — scoped permission tokens required before sensitive operations |
57
58
 
58
59
  ---
@@ -230,7 +231,7 @@ npm run demo -- --07
230
231
 
231
232
  ## Adapter System
232
233
 
233
- 13 adapters, zero adapter dependencies. You bring your own SDK objects.
234
+ 14 adapters, zero adapter dependencies. You bring your own SDK objects.
234
235
 
235
236
  | Adapter | Framework / Protocol | Register method |
236
237
  |---|---|---|
@@ -247,6 +248,7 @@ npm run demo -- --07
247
248
  | `AgnoAdapter` | Agno (formerly Phidata) | `registerAgent()`, `registerTeam()` |
248
249
  | `OpenClawAdapter` | OpenClaw | `registerSkill(name, skillRef)` |
249
250
  | `A2AAdapter` | Google A2A Protocol | `registerRemoteAgent(name, url)` |
251
+ | `CodexAdapter` | OpenAI Codex / gpt-4o / Codex CLI | `registerCodexAgent(name, config)` |
250
252
 
251
253
  **Streaming variants** (drop-in replacements with `.stream()` support):
252
254
 
@@ -265,7 +267,7 @@ Extend `BaseAdapter` (or `StreamingBaseAdapter` for streaming) to add your own i
265
267
 
266
268
  | Capability | Network-AI | LangGraph | CrewAI | AutoGen |
267
269
  |---|---|---|---|---|
268
- | Cross-framework agents in one swarm | ✅ 13 built-in adapters | ⚠️ Nodes can call any code; no adapter abstraction | ⚠️ Extensible via tools; CrewAI-native agents only | ⚠️ Extensible via plugins; AutoGen-native agents only |
270
+ | Cross-framework agents in one swarm | ✅ 14 built-in adapters | ⚠️ Nodes can call any code; no adapter abstraction | ⚠️ Extensible via tools; CrewAI-native agents only | ⚠️ Extensible via plugins; AutoGen-native agents only |
269
271
  | Atomic shared state (conflict-safe) | ✅ `propose → validate → commit` mutex | ⚠️ State passed between nodes; last-write-wins | ⚠️ Shared memory available; no conflict resolution | ⚠️ Shared context available; no conflict resolution |
270
272
  | Hard token ceiling per agent | ✅ `FederatedBudget` (first-class API) | ⚠️ Via callbacks / custom middleware | ⚠️ Via callbacks / custom middleware | ⚠️ Built-in token tracking in v0.4+; no swarm-level ceiling |
271
273
  | Permission gating before sensitive ops | ✅ `AuthGuardian` (built-in) | ⚠️ Possible via custom node logic | ⚠️ Possible via custom tools | ⚠️ Possible via custom middleware |
@@ -281,13 +283,14 @@ Extend `BaseAdapter` (or `StreamingBaseAdapter` for streaming) to add your own i
281
283
  npm run test:all # All suites in sequence
282
284
  npm test # Core orchestrator
283
285
  npm run test:security # Security module
284
- npm run test:adapters # All 13 adapters
286
+ npm run test:adapters # All 14 adapters
285
287
  npm run test:streaming # Streaming adapters
286
288
  npm run test:a2a # A2A protocol adapter
289
+ npm run test:codex # Codex adapter
287
290
  npm run test:priority # Priority & preemption
288
291
  ```
289
292
 
290
- **1,283 passing assertions across 15 test suites** (`npm run test:all`):
293
+ **1,334 passing assertions across 16 test suites** (`npm run test:all`):
291
294
 
292
295
  | Suite | Assertions | Covers |
293
296
  |---|---|---|
@@ -295,14 +298,15 @@ npm run test:priority # Priority & preemption
295
298
  | `test-phase5f.ts` | 127 | SSE transport, `McpCombinedBridge`, extended MCP tools |
296
299
  | `test-phase5g.ts` | 121 | CRDT backend, vector clocks, bidirectional sync |
297
300
  | `test-phase6.ts` | 121 | MCP server, control-plane tools, audit tools |
301
+ | `test-adapters.ts` | 140 | All 14 adapters, registry routing, integration, edge cases |
298
302
  | `test-phase5d.ts` | 117 | Pluggable backend (Redis, CRDT, Memory) |
299
- | `test-adapters.ts` | 139 | All 13 adapters, registry routing, integration, edge cases |
300
303
  | `test-standalone.ts` | 88 | Blackboard, auth, integration, persistence, parallelisation, quality gate |
301
304
  | `test-phase5e.ts` | 87 | Federated budget tracking |
302
305
  | `test-phase5c.ts` | 73 | Named multi-blackboard, isolation, backend options |
306
+ | `test-codex.ts` | 51 | Codex adapter: chat, completion, CLI, BYOC client, error paths |
303
307
  | `test-priority.ts` | 64 | Priority preemption, conflict resolution, backward compat |
304
- | `test-a2a.ts` | 34 | A2A protocol: register, execute, mock fetch, error paths |
305
- | `test-streaming.ts` | 31 | Streaming adapters, chunk shapes, fallback, collectStream |
308
+ | `test-a2a.ts` | 35 | A2A protocol: register, execute, mock fetch, error paths |
309
+ | `test-streaming.ts` | 32 | Streaming adapters, chunk shapes, fallback, collectStream |
306
310
  | `test-phase5b.ts` | 55 | Pluggable backend part 2, consistency levels |
307
311
  | `test-phase5.ts` | 42 | Named multi-blackboard base |
308
312
  | `test-security.ts` | 34 | Tokens, sanitization, rate limiting, encryption, audit |
package/bin/cli.ts ADDED
@@ -0,0 +1,299 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * network-ai CLI — full in-process control over blackboard, auth, budget, and audit.
4
+ * Imports core classes directly (Option B: no server required).
5
+ *
6
+ * Usage:
7
+ * npx network-ai <command> [options]
8
+ * network-ai <command> [options] (after npm install -g network-ai)
9
+ */
10
+
11
+ import { Command, Option } from 'commander';
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import * as readline from 'readline';
15
+
16
+ import { LockedBlackboard } from '../lib/locked-blackboard';
17
+ import { AuthGuardian } from '../index';
18
+ import { FederatedBudget } from '../lib/federated-budget';
19
+
20
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
21
+ const pkg = require('../package.json') as { version: string; name: string };
22
+
23
+ // ── helpers ───────────────────────────────────────────────────────────────────
24
+
25
+ function resolveData(opts: { data?: string }): string {
26
+ return path.resolve(opts.data ?? path.join(process.cwd(), 'data'));
27
+ }
28
+
29
+ function print(obj: unknown, asJson: boolean): void {
30
+ if (asJson) {
31
+ console.log(JSON.stringify(obj, null, 2));
32
+ } else if (typeof obj === 'string') {
33
+ console.log(obj);
34
+ } else {
35
+ console.log(JSON.stringify(obj, null, 2));
36
+ }
37
+ }
38
+
39
+ function die(msg: string): never {
40
+ console.error(`error: ${msg}`);
41
+ process.exit(1);
42
+ }
43
+
44
+ function tryParseJson(v: string): unknown {
45
+ try { return JSON.parse(v); } catch { return v; }
46
+ }
47
+
48
+ // ── root program ──────────────────────────────────────────────────────────────
49
+
50
+ const program = new Command();
51
+
52
+ program
53
+ .name('network-ai')
54
+ .description('Network-AI CLI — full control over blackboard, auth, budget, and audit')
55
+ .version(pkg.version, '-v, --version')
56
+ .enablePositionalOptions()
57
+ .addOption(new Option('--data <path>', 'path to data directory').default('./data'))
58
+ .addOption(new Option('--json', 'output raw JSON (useful for piping)'));
59
+
60
+ // ── bb (blackboard) ───────────────────────────────────────────────────────────
61
+
62
+ const bb = program.command('bb').description('Blackboard operations');
63
+
64
+ bb.command('get <key>')
65
+ .description('Read a value from the blackboard')
66
+ .action((key: string, _opts: Record<string, unknown>, cmd: Command) => {
67
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
68
+ const board = new LockedBlackboard(resolveData(g));
69
+ const entry = board.read(key);
70
+ if (!entry) die(`key not found: ${key}`);
71
+ print(g.json ? entry : entry.value, g.json);
72
+ });
73
+
74
+ bb.command('set <key> <value>')
75
+ .description('Write a value to the blackboard')
76
+ .option('--agent <id>', 'source agent id', 'cli')
77
+ .option('--ttl <seconds>', 'TTL in seconds', (v) => parseInt(v, 10))
78
+ .action((key: string, value: string, opts: { agent: string; ttl?: number }, cmd: Command) => {
79
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
80
+ const board = new LockedBlackboard(resolveData(g));
81
+ const entry = board.write(key, tryParseJson(value), opts.agent, opts.ttl);
82
+ print(g.json ? entry : `✓ set ${key}`, g.json);
83
+ });
84
+
85
+ bb.command('delete <key>')
86
+ .description('Delete a key from the blackboard')
87
+ .action((key: string, _opts: Record<string, unknown>, cmd: Command) => {
88
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
89
+ const board = new LockedBlackboard(resolveData(g));
90
+ const ok = board.delete(key);
91
+ if (!ok) die(`key not found: ${key}`);
92
+ print(g.json ? { deleted: key } : `✓ deleted ${key}`, g.json);
93
+ });
94
+
95
+ bb.command('list')
96
+ .description('List all keys on the blackboard')
97
+ .action((_opts: Record<string, unknown>, cmd: Command) => {
98
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
99
+ const board = new LockedBlackboard(resolveData(g));
100
+ const keys = board.listKeys();
101
+ print(g.json ? keys : keys.length ? keys.join('\n') : '(empty)', g.json);
102
+ });
103
+
104
+ bb.command('snapshot')
105
+ .description('Dump full blackboard state as JSON')
106
+ .action((_opts: Record<string, unknown>, cmd: Command) => {
107
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
108
+ const board = new LockedBlackboard(resolveData(g));
109
+ const snap: Record<string, unknown> = {};
110
+ for (const key of board.listKeys()) snap[key] = board.read(key);
111
+ console.log(JSON.stringify(snap, null, 2));
112
+ });
113
+
114
+ bb.command('propose <key> <value>')
115
+ .description('Propose a change and get back a change-id')
116
+ .option('--agent <id>', 'source agent id', 'cli')
117
+ .option('--ttl <seconds>', 'TTL in seconds', (v) => parseInt(v, 10))
118
+ .option('--priority <0-3>', 'priority: 0=low 1=normal 2=high 3=critical', (v) => parseInt(v, 10) as 0 | 1 | 2 | 3)
119
+ .action((key: string, value: string, opts: { agent: string; ttl?: number; priority?: 0 | 1 | 2 | 3 }, cmd: Command) => {
120
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
121
+ const board = new LockedBlackboard(resolveData(g));
122
+ const changeId = board.propose(key, tryParseJson(value), opts.agent, opts.ttl, opts.priority);
123
+ print(g.json ? { changeId } : `change-id: ${changeId}`, g.json);
124
+ });
125
+
126
+ bb.command('commit <changeId>')
127
+ .description('Validate and commit a proposed change')
128
+ .option('--agent <id>', 'validator agent id', 'cli')
129
+ .action((changeId: string, opts: { agent: string }, cmd: Command) => {
130
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
131
+ const board = new LockedBlackboard(resolveData(g));
132
+ const valid = board.validate(changeId, opts.agent);
133
+ if (!valid) die(`change ${changeId} has conflicts — run 'bb abort' to cancel`);
134
+ const result = board.commit(changeId);
135
+ if (!result.success) die(`commit failed: ${result.message}`);
136
+ print(g.json ? result : `✓ committed ${changeId}: ${result.message}`, g.json);
137
+ });
138
+
139
+ bb.command('abort <changeId>')
140
+ .description('Abort a pending proposed change')
141
+ .action((changeId: string, _opts: Record<string, unknown>, cmd: Command) => {
142
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
143
+ const board = new LockedBlackboard(resolveData(g));
144
+ const ok = board.abort(changeId);
145
+ if (!ok) die(`change not found: ${changeId}`);
146
+ print(g.json ? { aborted: changeId } : `✓ aborted ${changeId}`, g.json);
147
+ });
148
+
149
+ // ── auth ──────────────────────────────────────────────────────────────────────
150
+
151
+ const auth = program.command('auth').description('Permission and token operations');
152
+
153
+ auth.command('token <agentId>')
154
+ .description('Issue a permission token for an agent')
155
+ .option('--resource <type>', 'resource type to grant', 'blackboard')
156
+ .option('--justification <text>', 'justification text', 'CLI-issued token')
157
+ .option('--scope <scope>', 'permission scope')
158
+ .action(async (agentId: string, opts: { resource: string; justification: string; scope?: string }, cmd: Command) => {
159
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
160
+ const auditPath = path.join(resolveData(g), 'audit_log.jsonl');
161
+ const guardian = new AuthGuardian({ auditLogPath: auditPath });
162
+ guardian.registerAgentTrust({ agentId, trustLevel: 1, allowedResources: [opts.resource] });
163
+ const grant = await guardian.requestPermission(agentId, opts.resource, opts.justification, opts.scope);
164
+ if (g.json) {
165
+ print(grant, true);
166
+ } else {
167
+ console.log(`token: ${grant.grantToken ?? '(none)'}`);
168
+ console.log(`granted: ${grant.granted}`);
169
+ console.log(`expires: ${grant.expiresAt ?? 'never'}`);
170
+ if (!grant.granted && grant.reason) console.log(`reason: ${grant.reason}`);
171
+ }
172
+ });
173
+
174
+ auth.command('revoke <token>')
175
+ .description('Revoke a permission token')
176
+ .action((token: string, _opts: Record<string, unknown>, cmd: Command) => {
177
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
178
+ const auditPath = path.join(resolveData(g), 'audit_log.jsonl');
179
+ const guardian = new AuthGuardian({ auditLogPath: auditPath });
180
+ guardian.revokeToken(token);
181
+ print(g.json ? { revoked: token } : `✓ revoked`, g.json);
182
+ });
183
+
184
+ auth.command('check <token> <permission>')
185
+ .description('Check if a token grants a permission (exits 0=yes, 1=no)')
186
+ .action((token: string, permission: string, _opts: Record<string, unknown>, cmd: Command) => {
187
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
188
+ const auditPath = path.join(resolveData(g), 'audit_log.jsonl');
189
+ const guardian = new AuthGuardian({ auditLogPath: auditPath });
190
+ const valid = guardian.validateToken(token);
191
+ const violation = valid ? guardian.enforceRestrictions(token, { type: permission }) : 'invalid or expired token';
192
+ const allowed = valid && violation === null;
193
+ print(
194
+ g.json ? { allowed, permission, reason: violation ?? null } : (allowed ? `✓ allowed` : `✗ denied: ${violation}`),
195
+ g.json,
196
+ );
197
+ process.exit(allowed ? 0 : 1);
198
+ });
199
+
200
+ // ── budget ────────────────────────────────────────────────────────────────────
201
+
202
+ const budgetCmd = program.command('budget').description('Token budget operations');
203
+
204
+ budgetCmd.command('status [agentId]')
205
+ .description('Show token budget status')
206
+ .option('--ceiling <n>', 'total token ceiling', (v) => parseInt(v, 10))
207
+ .action((agentId: string | undefined, opts: { ceiling?: number }, cmd: Command) => {
208
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
209
+ const ceiling = opts.ceiling ?? 100_000;
210
+ const fed = new FederatedBudget({ ceiling });
211
+ if (agentId) {
212
+ const spent = fed.getAgentSpent(agentId);
213
+ const snap = { agentId, spent, remaining: fed.remaining(), ceiling: fed.getCeiling() };
214
+ print(g.json ? snap : `agent: ${agentId}\nspent: ${snap.spent}\nremaining: ${snap.remaining}\nceiling: ${snap.ceiling}`, g.json);
215
+ } else {
216
+ const snap = { totalSpent: fed.getTotalSpent(), remaining: fed.remaining(), ceiling: fed.getCeiling(), byAgent: fed.getSpendLog() };
217
+ print(g.json ? snap : `total-spent: ${snap.totalSpent}\nremaining: ${snap.remaining}\nceiling: ${snap.ceiling}`, g.json);
218
+ }
219
+ });
220
+
221
+ budgetCmd.command('set-ceiling <amount>')
222
+ .description('Update the token ceiling')
223
+ .action((amount: string, _opts: Record<string, unknown>, cmd: Command) => {
224
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
225
+ const n = parseInt(amount, 10);
226
+ if (isNaN(n) || n <= 0) die('amount must be a positive integer');
227
+ const fed = new FederatedBudget({ ceiling: n });
228
+ fed.setCeiling(n);
229
+ print(g.json ? { ceiling: n } : `✓ ceiling set to ${n}`, g.json);
230
+ });
231
+
232
+ // ── audit ─────────────────────────────────────────────────────────────────────
233
+
234
+ const auditCmd = program.command('audit').description('Audit log operations');
235
+
236
+ function getAuditLogPath(dataDir: string): string {
237
+ return path.join(dataDir, 'audit_log.jsonl');
238
+ }
239
+
240
+ auditCmd.command('log')
241
+ .description('Print audit log entries')
242
+ .option('--limit <n>', 'show last N entries', (v) => parseInt(v, 10))
243
+ .action((opts: { limit?: number }, cmd: Command) => {
244
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
245
+ const logFile = getAuditLogPath(resolveData(g));
246
+ if (!fs.existsSync(logFile)) die(`audit log not found: ${logFile}`);
247
+ const lines = fs.readFileSync(logFile, 'utf-8').trim().split('\n').filter(Boolean);
248
+ const slice = opts.limit ? lines.slice(-opts.limit) : lines;
249
+ if (g.json) {
250
+ console.log(JSON.stringify(slice.map(l => { try { return JSON.parse(l); } catch { return l; } }), null, 2));
251
+ } else {
252
+ if (slice.length === 0) { console.log('(no entries)'); return; }
253
+ slice.forEach(l => console.log(l));
254
+ }
255
+ });
256
+
257
+ auditCmd.command('tail')
258
+ .description('Live-stream new audit log entries (Ctrl+C to stop)')
259
+ .action((_opts: Record<string, unknown>, cmd: Command) => {
260
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
261
+ const logFile = getAuditLogPath(resolveData(g));
262
+ if (!fs.existsSync(logFile)) die(`audit log not found: ${logFile}`);
263
+ let size = fs.statSync(logFile).size;
264
+ console.log(`tailing ${logFile} — Ctrl+C to stop`);
265
+ const interval = setInterval(() => {
266
+ try {
267
+ const newSize = fs.statSync(logFile).size;
268
+ if (newSize > size) {
269
+ const fd = fs.openSync(logFile, 'r');
270
+ const buf = Buffer.alloc(newSize - size);
271
+ fs.readSync(fd, buf, 0, buf.length, size);
272
+ fs.closeSync(fd);
273
+ buf.toString('utf-8').trim().split('\n').filter(Boolean).forEach(l => console.log(l));
274
+ size = newSize;
275
+ }
276
+ } catch { /* file may be briefly locked */ }
277
+ }, 500);
278
+ process.on('SIGINT', () => { clearInterval(interval); process.exit(0); });
279
+ });
280
+
281
+ auditCmd.command('clear')
282
+ .description('Clear the audit log (prompts for confirmation)')
283
+ .option('--yes', 'skip confirmation prompt')
284
+ .action(async (opts: { yes?: boolean }, cmd: Command) => {
285
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
286
+ const logFile = getAuditLogPath(resolveData(g));
287
+ if (!opts.yes) {
288
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
289
+ const answer = await new Promise<string>(r => rl.question(`Clear ${logFile}? [y/N] `, r));
290
+ rl.close();
291
+ if (answer.toLowerCase() !== 'y') { console.log('Aborted.'); return; }
292
+ }
293
+ fs.writeFileSync(logFile, '');
294
+ print(g.json ? { cleared: logFile } : `✓ cleared ${logFile}`, g.json);
295
+ });
296
+
297
+ // ── parse ─────────────────────────────────────────────────────────────────────
298
+
299
+ program.parse(process.argv);
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Codex Adapter
3
+ *
4
+ * Integrates OpenAI Codex (code-davinci-002, gpt-4o, o4-mini, or any
5
+ * completions/chat-completions endpoint) with the SwarmOrchestrator.
6
+ *
7
+ * Supports three modes:
8
+ * - `completion` — legacy /v1/completions (code-davinci-002 style)
9
+ * - `chat` — /v1/chat/completions (gpt-4o, o4-mini, etc.)
10
+ * - `cli` — wraps an OpenAI Codex CLI process via a user-supplied
11
+ * executor function (for the codex CLI tool)
12
+ *
13
+ * Usage — chat mode:
14
+ * const adapter = new CodexAdapter();
15
+ * adapter.registerCodexAgent('refactor', {
16
+ * mode: 'chat',
17
+ * model: 'gpt-4o',
18
+ * systemPrompt: 'You are a refactoring assistant.',
19
+ * apiKey: process.env.OPENAI_API_KEY,
20
+ * });
21
+ *
22
+ * Usage — CLI mode (Codex CLI tool):
23
+ * adapter.registerCodexAgent('codex-cli', {
24
+ * mode: 'cli',
25
+ * executor: async (prompt) => myCodexCLIWrapper(prompt),
26
+ * });
27
+ *
28
+ * Usage — custom client (bring-your-own OpenAI SDK):
29
+ * adapter.registerCodexAgent('analyst', {
30
+ * mode: 'chat',
31
+ * model: 'gpt-4o',
32
+ * client: openaiInstance, // any object with .chat.completions.create()
33
+ * });
34
+ *
35
+ * @module CodexAdapter
36
+ * @version 1.0.0
37
+ */
38
+ import { BaseAdapter } from './base-adapter';
39
+ import type { AdapterCapabilities, AgentPayload, AgentContext, AgentResult } from '../types/agent-adapter';
40
+ /** Execution mode for a Codex agent */
41
+ export type CodexMode = 'completion' | 'chat' | 'cli';
42
+ /**
43
+ * Minimal interface for the OpenAI SDK's chat completions —
44
+ * compatible with `new OpenAI().chat.completions`.
45
+ * Users supply their own SDK instance; no hard dependency.
46
+ */
47
+ export interface CodexChatClient {
48
+ create(params: {
49
+ model: string;
50
+ messages: Array<{
51
+ role: string;
52
+ content: string;
53
+ }>;
54
+ max_tokens?: number;
55
+ temperature?: number;
56
+ stop?: string[];
57
+ }): Promise<{
58
+ choices: Array<{
59
+ message?: {
60
+ content?: string | null;
61
+ };
62
+ text?: string;
63
+ }>;
64
+ usage?: {
65
+ prompt_tokens: number;
66
+ completion_tokens: number;
67
+ total_tokens: number;
68
+ };
69
+ }>;
70
+ }
71
+ /**
72
+ * Minimal interface for the OpenAI SDK's legacy completions —
73
+ * compatible with `new OpenAI().completions`.
74
+ */
75
+ export interface CodexCompletionClient {
76
+ create(params: {
77
+ model: string;
78
+ prompt: string;
79
+ max_tokens?: number;
80
+ temperature?: number;
81
+ stop?: string[];
82
+ }): Promise<{
83
+ choices: Array<{
84
+ text?: string;
85
+ }>;
86
+ usage?: {
87
+ prompt_tokens: number;
88
+ completion_tokens: number;
89
+ total_tokens: number;
90
+ };
91
+ }>;
92
+ }
93
+ /**
94
+ * A user-supplied executor for Codex CLI mode.
95
+ * Receives the assembled prompt, returns the CLI output string.
96
+ */
97
+ export type CodexCLIExecutor = (prompt: string, options?: Record<string, unknown>) => Promise<string>;
98
+ /** Configuration for a registered Codex agent */
99
+ export interface CodexAgentConfig {
100
+ /** Execution mode (default: 'chat') */
101
+ mode?: CodexMode;
102
+ /** Model name — e.g. 'gpt-4o', 'o4-mini', 'code-davinci-002' */
103
+ model?: string;
104
+ /** OpenAI API key — falls back to OPENAI_API_KEY env var */
105
+ apiKey?: string;
106
+ /** Base URL override for Azure OpenAI, proxies, or self-hosted endpoints */
107
+ baseUrl?: string;
108
+ /** System-level prompt prepended to every request (chat mode only) */
109
+ systemPrompt?: string;
110
+ /** Maximum tokens in the completion */
111
+ maxTokens?: number;
112
+ /** Temperature (0–2). Default: 0 for code tasks */
113
+ temperature?: number;
114
+ /** Stop sequences */
115
+ stop?: string[];
116
+ /**
117
+ * Bring-your-own OpenAI SDK chat completions instance.
118
+ * If supplied, apiKey / baseUrl are ignored and this client is used directly.
119
+ * Typically `new OpenAI().chat.completions` or `openai.chat.completions`.
120
+ */
121
+ client?: CodexChatClient | CodexCompletionClient;
122
+ /**
123
+ * Executor function for CLI mode.
124
+ * Required when mode === 'cli'.
125
+ */
126
+ executor?: CodexCLIExecutor;
127
+ /** Additional headers to send with fetch-based requests */
128
+ headers?: Record<string, string>;
129
+ }
130
+ /**
131
+ * Adapter that connects OpenAI Codex / code-focused models to the
132
+ * SwarmOrchestrator. Supports chat completions, legacy completions,
133
+ * and the Codex CLI executor interface.
134
+ */
135
+ export declare class CodexAdapter extends BaseAdapter {
136
+ readonly name = "codex";
137
+ readonly version = "1.0.0";
138
+ private agents;
139
+ get capabilities(): AdapterCapabilities;
140
+ /**
141
+ * Register a Codex-powered agent.
142
+ *
143
+ * @param agentId Unique identifier used in `delegateTask` calls.
144
+ * @param config Codex agent configuration.
145
+ */
146
+ registerCodexAgent(agentId: string, config?: CodexAgentConfig): void;
147
+ executeAgent(agentId: string, payload: AgentPayload, context: AgentContext): Promise<AgentResult>;
148
+ private _executeChat;
149
+ private _executeCompletion;
150
+ private _executeCLI;
151
+ }
152
+ //# sourceMappingURL=codex-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-adapter.d.ts","sourceRoot":"","sources":["../../adapters/codex-adapter.ts"],"names":[],"mappings":"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"}