agentbnb 4.0.0 → 4.0.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.
Files changed (44) hide show
  1. package/README.md +2 -0
  2. package/dist/{card-IE5UV5QX.js → card-RSGDCHCV.js} +11 -4
  3. package/dist/chunk-3MJT4PZG.js +50 -0
  4. package/dist/{chunk-HEVXCYCY.js → chunk-4P3EMGL4.js} +61 -24
  5. package/dist/chunk-5AH3CMOX.js +62 -0
  6. package/dist/{chunk-QO67IGCW.js → chunk-5KFI5X7B.js} +1 -1
  7. package/dist/chunk-75OC6E4F.js +33 -0
  8. package/dist/{chunk-CUVIWPQO.js → chunk-7NA43XCG.js} +7 -6
  9. package/dist/{conduct-IQYAT6ZU.js → chunk-BH6WGYFB.js} +70 -33
  10. package/dist/{chunk-QVV2P3FN.js → chunk-DNWT5FZQ.js} +22 -2
  11. package/dist/chunk-FF226TIV.js +148 -0
  12. package/dist/{chunk-UJWYE7VL.js → chunk-GGYC5U2Z.js} +28 -111
  13. package/dist/chunk-HH24WMFN.js +373 -0
  14. package/dist/{websocket-client-5TIQDYQ4.js → chunk-JOY533UH.js} +38 -4
  15. package/dist/chunk-QITOPASZ.js +96 -0
  16. package/dist/{chunk-3Y36WQDV.js → chunk-QT7TEVNV.js} +14 -2
  17. package/dist/{chunk-UOGDK2S2.js → chunk-T7NS2J2B.js} +1 -1
  18. package/dist/{chunk-XA63SD4T.js → chunk-WGZ5AGOX.js} +37 -0
  19. package/dist/{chunk-RSX4SCPN.js → chunk-XND2DWTZ.js} +4 -3
  20. package/dist/cli/index.js +2924 -835
  21. package/dist/{client-IOTK6GOS.js → client-T5MTY3CS.js} +3 -3
  22. package/dist/conduct-GZQNFTRP.js +19 -0
  23. package/dist/conduct-N52JX7RT.js +52 -0
  24. package/dist/{conductor-mode-XU7ONJWC.js → conductor-mode-XUWGR4ZE.js} +16 -9
  25. package/dist/execute-PNGQOMYO.js +10 -0
  26. package/dist/index.d.ts +1148 -915
  27. package/dist/index.js +589 -127
  28. package/dist/{peers-G36URZYB.js → peers-K7FSHPN3.js} +2 -1
  29. package/dist/request-4GQSSM4B.js +196 -0
  30. package/dist/serve-skill-TPHZH6BS.js +104 -0
  31. package/dist/server-365V3GYD.js +295 -0
  32. package/dist/websocket-client-6IIDGXKB.js +7 -0
  33. package/package.json +3 -6
  34. package/skills/agentbnb/HEARTBEAT.rules.md +47 -0
  35. package/skills/agentbnb/SKILL.md +166 -0
  36. package/skills/agentbnb/auto-request.ts +14 -0
  37. package/skills/agentbnb/auto-share.ts +10 -0
  38. package/skills/agentbnb/bootstrap.test.ts +323 -0
  39. package/skills/agentbnb/bootstrap.ts +126 -0
  40. package/skills/agentbnb/credit-mgr.ts +11 -0
  41. package/skills/agentbnb/gateway.ts +12 -0
  42. package/skills/agentbnb/install.sh +210 -0
  43. package/dist/chunk-BEI5MTNZ.js +0 -91
  44. package/dist/execute-GDGBU6DJ.js +0 -10
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: agentbnb
3
+ description: "P2P capability sharing for AI agents — earn credits by sharing idle APIs, spend credits to request capabilities from peer agents. Use when an agent needs to join the AgentBnB network, publish capability cards, manage credits, or request skills from peers."
4
+ license: MIT
5
+ compatibility: "Requires Node.js >= 20 and pnpm. Designed for OpenClaw agents. Compatible with Claude Code, Gemini CLI, and other AgentSkills-compatible tools."
6
+ on_install: auto
7
+ metadata:
8
+ author: "Cheng Wen Chen"
9
+ version: "3.0.0"
10
+ tags: "ai-agent-skill,claude-code,agent-skills,p2p,capability-sharing"
11
+ ---
12
+
13
+ This skill connects your agent to the AgentBnB P2P capability sharing network.
14
+
15
+ ## Quick Start
16
+
17
+ **Step 1: Run the install script.**
18
+
19
+ ```bash
20
+ bash install.sh
21
+ ```
22
+
23
+ This installs the AgentBnB CLI, initializes `~/.agentbnb/` config, and syncs your SOUL.md if present.
24
+
25
+ **Step 2: Import `activate()` from `bootstrap.ts` and bring your agent online.**
26
+
27
+ ```typescript
28
+ import { activate, deactivate } from './bootstrap.js';
29
+
30
+ const ctx = await activate({
31
+ owner: 'your-agent-id',
32
+ soulMdPath: './SOUL.md',
33
+ registryDbPath: './registry.db', // omit to use ~/.agentbnb/registry.db
34
+ creditDbPath: './credit.db', // omit to use ~/.agentbnb/credit.db
35
+ gatewayPort: 7700,
36
+ gatewayToken: 'your-bearer-token', // omit to auto-generate
37
+ silent: false,
38
+ });
39
+
40
+ // ctx.runtime — AgentRuntime (DB handles + background jobs)
41
+ // ctx.gateway — Fastify HTTP server on gatewayPort
42
+ // ctx.idleMonitor — IdleMonitor polling on 60s cadence
43
+ // ctx.card — Published CapabilityCardV2 from SOUL.md
44
+ ```
45
+
46
+ **Step 3: When done, call `deactivate()` to clean up all resources.**
47
+
48
+ ```typescript
49
+ await deactivate(ctx);
50
+ // Closes gateway, stops cron jobs, closes DB handles. Idempotent.
51
+ ```
52
+
53
+ ## On Install
54
+
55
+ `install.sh` performs the following steps automatically:
56
+
57
+ 1. Checks Node.js >= 20 is available (exits with error if not).
58
+ 2. Checks pnpm is available; falls back to npm if not.
59
+ 3. Runs `pnpm install -g agentbnb` (or `npm install -g agentbnb`).
60
+ 4. Runs `agentbnb init --yes` to create `~/.agentbnb/` with default config.
61
+ 5. Runs `agentbnb openclaw sync` if SOUL.md is found in the current or parent directory.
62
+ 6. Prints a success summary with next steps.
63
+
64
+ ## Programmatic API
65
+
66
+ Use `activate()` and `deactivate()` from `bootstrap.ts` for full lifecycle control.
67
+
68
+ ### `activate(config: BootstrapConfig): Promise<BootstrapContext>`
69
+
70
+ Brings an agent fully online in order: Runtime → publishCard → gateway.listen → IdleMonitor.
71
+
72
+ **BootstrapConfig fields:**
73
+
74
+ | Field | Type | Default | Purpose |
75
+ |---|---|---|---|
76
+ | `owner` | `string` | required | Agent owner identifier |
77
+ | `soulMdPath` | `string` | required | Absolute path to SOUL.md |
78
+ | `registryDbPath` | `string` | `~/.agentbnb/registry.db` | Registry SQLite path |
79
+ | `creditDbPath` | `string` | `~/.agentbnb/credit.db` | Credit SQLite path |
80
+ | `gatewayPort` | `number` | `7700` | HTTP port for incoming capability requests |
81
+ | `gatewayToken` | `string` | auto `randomUUID()` | Bearer token for gateway auth |
82
+ | `handlerUrl` | `string` | `http://localhost:{gatewayPort}` | URL for capability forwarding |
83
+ | `autonomyConfig` | `AutonomyConfig` | `DEFAULT_AUTONOMY_CONFIG` (Tier 3) | Tier thresholds |
84
+ | `silent` | `boolean` | `false` | Suppress gateway logs |
85
+
86
+ Throws `AgentBnBError` with code `FILE_NOT_FOUND` if `soulMdPath` does not exist.
87
+
88
+ **BootstrapContext fields:**
89
+
90
+ | Field | Type | Description |
91
+ |---|---|---|
92
+ | `runtime` | `AgentRuntime` | DB handles + background job registry |
93
+ | `gateway` | `FastifyInstance` | HTTP gateway accepting capability requests |
94
+ | `idleMonitor` | `IdleMonitor` | Cron job monitoring idle rates every 60s |
95
+ | `card` | `CapabilityCardV2` | Published card derived from SOUL.md |
96
+
97
+ ### `deactivate(ctx: BootstrapContext): Promise<void>`
98
+
99
+ Tears down all active components: `gateway.close()` then `runtime.shutdown()`.
100
+ Idempotent — safe to call multiple times without throwing.
101
+
102
+ ## Autonomy Rules
103
+
104
+ Full rules block is in `HEARTBEAT.rules.md`. Copy into your `HEARTBEAT.md`, or run:
105
+
106
+ ```bash
107
+ agentbnb openclaw rules
108
+ ```
109
+
110
+ This outputs a rules block using your actual configured thresholds (not the example defaults).
111
+
112
+ **Summary of the 3 tiers:**
113
+
114
+ - **Tier 1** (< tier1 credits): Auto-execute, no notification.
115
+ - **Tier 2** (tier1–tier2 credits): Execute and notify owner after.
116
+ - **Tier 3** (> tier2 credits): Ask owner before executing. (Default on fresh installs.)
117
+
118
+ **Reserve floor:** Maintain a minimum credit balance (default 20). When balance ≤ reserve, auto-request is blocked. Increase sharing priority to recover.
119
+
120
+ **Idle sharing:** When a skill's `idle_rate` exceeds 70%, `IdleMonitor` auto-shares it according to the active tier.
121
+
122
+ Configure thresholds:
123
+
124
+ ```bash
125
+ agentbnb config set tier1 10 # auto-execute under 10 credits
126
+ agentbnb config set tier2 50 # notify-after under 50 credits
127
+ agentbnb config set reserve 20 # keep 20 credit reserve
128
+ ```
129
+
130
+ ## CLI Reference
131
+
132
+ ```bash
133
+ agentbnb serve # Start accepting incoming capability requests
134
+ agentbnb openclaw sync # Parse SOUL.md and publish capability card to registry
135
+ agentbnb openclaw status # Show sync state, credit balance, idle rates
136
+ agentbnb openclaw rules # Emit HEARTBEAT.md rules block with real thresholds
137
+ agentbnb config set tier1 <N> # Set Tier 1 credit threshold
138
+ agentbnb config set tier2 <N> # Set Tier 2 credit threshold
139
+ agentbnb config set reserve <N> # Set minimum credit reserve floor
140
+ agentbnb discover # Find peers on the local network via mDNS
141
+ agentbnb request --query "..." # Manually request a capability from the network
142
+ ```
143
+
144
+ ## Adapters
145
+
146
+ Use individual adapters if you need custom wiring. `bootstrap.ts` composes all of these.
147
+
148
+ | Adapter | Export | Purpose |
149
+ |---|---|---|
150
+ | `gateway.ts` | `AgentRuntime`, `createGatewayServer` | HTTP gateway for receiving requests |
151
+ | `auto-share.ts` | `IdleMonitor` | Per-skill idle rate polling + auto-share |
152
+ | `auto-request.ts` | `AutoRequestor` | Peer scoring + budget-gated capability requests |
153
+ | `credit-mgr.ts` | `BudgetManager`, `getBalance` | Credit reserve floor + balance queries |
154
+
155
+ ```typescript
156
+ // Example: use IdleMonitor directly without full bootstrap
157
+ import { IdleMonitor } from './auto-share.js';
158
+
159
+ const monitor = new IdleMonitor({
160
+ owner: 'my-agent',
161
+ db: runtime.registryDb,
162
+ autonomyConfig: config.autonomy,
163
+ });
164
+ const job = monitor.start();
165
+ runtime.registerJob(job);
166
+ ```
@@ -0,0 +1,14 @@
1
+ /**
2
+ * AgentBnB Auto-Request adapter for OpenClaw skill integration.
3
+ * Delegates to AutoRequestor from src/autonomy/.
4
+ * This is a thin re-export wrapper — no business logic lives here.
5
+ */
6
+ import { AutoRequestor } from '../../src/autonomy/auto-request.js';
7
+ import type {
8
+ AutoRequestOptions,
9
+ CapabilityNeed,
10
+ AutoRequestResult,
11
+ } from '../../src/autonomy/auto-request.js';
12
+
13
+ export { AutoRequestor };
14
+ export type { AutoRequestOptions, CapabilityNeed, AutoRequestResult };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * AgentBnB Auto-Share adapter for OpenClaw skill integration.
3
+ * Delegates to IdleMonitor from src/autonomy/.
4
+ * This is a thin re-export wrapper — no business logic lives here.
5
+ */
6
+ import { IdleMonitor } from '../../src/autonomy/idle-monitor.js';
7
+ import type { IdleMonitorOptions } from '../../src/autonomy/idle-monitor.js';
8
+
9
+ export { IdleMonitor };
10
+ export type { IdleMonitorOptions };
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Integration test for bootstrap.ts activate()/deactivate() lifecycle.
3
+ *
4
+ * Tests the full lifecycle using real implementations with in-memory DBs:
5
+ * activate() -> runtime + card published + gateway listening + IdleMonitor running
6
+ * deactivate() -> gateway closed + runtime shutdown + resources cleaned up
7
+ *
8
+ * No mocks — this is an end-to-end integration test.
9
+ */
10
+
11
+ import { describe, it, expect, afterEach } from 'vitest';
12
+ import { mkdtempSync, writeFileSync, rmSync, existsSync } from 'node:fs';
13
+ import { tmpdir } from 'node:os';
14
+ import { join } from 'node:path';
15
+
16
+ import { activate, deactivate } from './bootstrap.js';
17
+ import type { BootstrapContext } from './bootstrap.js';
18
+ import { IdleMonitor } from '../../src/autonomy/idle-monitor.js';
19
+ import { loadIdentity } from '../../src/identity/identity.js';
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Test fixture: minimal SOUL.md with 2 skills
23
+ // ---------------------------------------------------------------------------
24
+ const SOUL_MD_CONTENT = `# Test Agent
25
+
26
+ A test agent for integration testing.
27
+
28
+ ## Code Review
29
+ Reviews code for quality and bugs.
30
+
31
+ ## Translation
32
+ Translates text between languages.
33
+ `;
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Test suite
37
+ // ---------------------------------------------------------------------------
38
+
39
+ describe('bootstrap activate/deactivate lifecycle', () => {
40
+ let tmpDir: string | undefined;
41
+ let ctx: BootstrapContext | undefined;
42
+
43
+ /**
44
+ * Create a fresh temp dir with a SOUL.md file.
45
+ * Returns the absolute path to the SOUL.md file.
46
+ */
47
+ function setupSoulMd(): string {
48
+ tmpDir = mkdtempSync(join(tmpdir(), 'agentbnb-test-'));
49
+ const path = join(tmpDir, 'SOUL.md');
50
+ writeFileSync(path, SOUL_MD_CONTENT, 'utf8');
51
+ return path;
52
+ }
53
+
54
+ // Ensure all resources are torn down after every test
55
+ afterEach(async () => {
56
+ if (ctx) {
57
+ await deactivate(ctx).catch(() => undefined);
58
+ ctx = undefined;
59
+ }
60
+ if (tmpDir) {
61
+ rmSync(tmpDir, { recursive: true, force: true });
62
+ tmpDir = undefined;
63
+ }
64
+ });
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Test 1: activate() returns BootstrapContext with all components
68
+ // ---------------------------------------------------------------------------
69
+ it('activate() returns BootstrapContext with all components', async () => {
70
+ const soulMdPath = setupSoulMd();
71
+
72
+ ctx = await activate({
73
+ owner: 'test-agent',
74
+ soulMdPath,
75
+ registryDbPath: ':memory:',
76
+ creditDbPath: ':memory:',
77
+ gatewayPort: 0,
78
+ silent: true,
79
+ });
80
+
81
+ // runtime has both DB handles
82
+ expect(ctx.runtime).toBeDefined();
83
+ expect(ctx.runtime.registryDb).toBeDefined();
84
+ expect(ctx.runtime.creditDb).toBeDefined();
85
+
86
+ // gateway is a Fastify instance (has inject method)
87
+ expect(ctx.gateway).toBeDefined();
88
+ expect(typeof ctx.gateway.inject).toBe('function');
89
+
90
+ // idleMonitor is an IdleMonitor instance
91
+ expect(ctx.idleMonitor).toBeInstanceOf(IdleMonitor);
92
+
93
+ // card has correct structure: spec_version 2.0, 2 skills
94
+ expect(ctx.card.spec_version).toBe('2.0');
95
+ expect(Array.isArray(ctx.card.skills)).toBe(true);
96
+ expect(ctx.card.skills!.length).toBe(2);
97
+ });
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Test 2: activate() publishes card from SOUL.md into registry
101
+ // ---------------------------------------------------------------------------
102
+ it('activate() publishes card from SOUL.md into registry', async () => {
103
+ const soulMdPath = setupSoulMd();
104
+
105
+ ctx = await activate({
106
+ owner: 'test-agent',
107
+ soulMdPath,
108
+ registryDbPath: ':memory:',
109
+ creditDbPath: ':memory:',
110
+ gatewayPort: 0,
111
+ silent: true,
112
+ });
113
+
114
+ // Query the registry DB for capability_cards owned by this agent
115
+ const rows = ctx.runtime.registryDb
116
+ .prepare('SELECT id, owner, data FROM capability_cards WHERE owner = ?')
117
+ .all('test-agent') as Array<{ id: string; owner: string; data: string }>;
118
+
119
+ expect(rows.length).toBe(1);
120
+ expect(rows[0].owner).toBe('test-agent');
121
+
122
+ // Parse the card data and verify both skills are present
123
+ const cardData = JSON.parse(rows[0].data) as {
124
+ skills?: Array<{ name: string }>;
125
+ };
126
+ const skillNames = (cardData.skills ?? []).map((s) => s.name);
127
+ expect(skillNames).toContain('Code Review');
128
+ expect(skillNames).toContain('Translation');
129
+ });
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // Test 3: activate() starts gateway that responds to health check
133
+ // ---------------------------------------------------------------------------
134
+ it('activate() starts gateway that responds to health check', async () => {
135
+ const soulMdPath = setupSoulMd();
136
+
137
+ ctx = await activate({
138
+ owner: 'test-agent',
139
+ soulMdPath,
140
+ registryDbPath: ':memory:',
141
+ creditDbPath: ':memory:',
142
+ gatewayPort: 0,
143
+ silent: true,
144
+ });
145
+
146
+ // Use Fastify inject — no real HTTP connection needed
147
+ const response = await ctx.gateway.inject({
148
+ method: 'GET',
149
+ url: '/health',
150
+ });
151
+
152
+ expect(response.statusCode).toBe(200);
153
+ const body = JSON.parse(response.body) as { status: string };
154
+ expect(body.status).toBe('ok');
155
+ });
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // Test 4: activate() registers IdleMonitor job in runtime.jobs
159
+ // ---------------------------------------------------------------------------
160
+ it('activate() registers IdleMonitor job in runtime.jobs', async () => {
161
+ const soulMdPath = setupSoulMd();
162
+
163
+ ctx = await activate({
164
+ owner: 'test-agent',
165
+ soulMdPath,
166
+ registryDbPath: ':memory:',
167
+ creditDbPath: ':memory:',
168
+ gatewayPort: 0,
169
+ silent: true,
170
+ });
171
+
172
+ // At least one cron job registered (the IdleMonitor's polling job)
173
+ expect(ctx.runtime.jobs.length).toBeGreaterThanOrEqual(1);
174
+ });
175
+
176
+ // ---------------------------------------------------------------------------
177
+ // Test 5: deactivate() sets runtime.isDraining to true
178
+ // ---------------------------------------------------------------------------
179
+ it('deactivate() sets runtime.isDraining to true', async () => {
180
+ const soulMdPath = setupSoulMd();
181
+
182
+ ctx = await activate({
183
+ owner: 'test-agent',
184
+ soulMdPath,
185
+ registryDbPath: ':memory:',
186
+ creditDbPath: ':memory:',
187
+ gatewayPort: 0,
188
+ silent: true,
189
+ });
190
+
191
+ expect(ctx.runtime.isDraining).toBe(false);
192
+
193
+ await deactivate(ctx);
194
+ // Clear so afterEach doesn't double-deactivate
195
+ const savedCtx = ctx;
196
+ ctx = undefined;
197
+
198
+ expect(savedCtx.runtime.isDraining).toBe(true);
199
+ });
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // Test 6: deactivate() closes DB handles
203
+ // ---------------------------------------------------------------------------
204
+ it('deactivate() closes DB handles', async () => {
205
+ const soulMdPath = setupSoulMd();
206
+
207
+ ctx = await activate({
208
+ owner: 'test-agent',
209
+ soulMdPath,
210
+ registryDbPath: ':memory:',
211
+ creditDbPath: ':memory:',
212
+ gatewayPort: 0,
213
+ silent: true,
214
+ });
215
+
216
+ // Capture reference before clearing ctx
217
+ const runtime = ctx.runtime;
218
+ await deactivate(ctx);
219
+ ctx = undefined;
220
+
221
+ // Queries should throw after DB handles are closed
222
+ expect(() => {
223
+ runtime.registryDb.prepare('SELECT 1').get();
224
+ }).toThrow();
225
+ });
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Test 7: deactivate() is idempotent
229
+ // ---------------------------------------------------------------------------
230
+ it('deactivate() is idempotent', async () => {
231
+ const soulMdPath = setupSoulMd();
232
+
233
+ ctx = await activate({
234
+ owner: 'test-agent',
235
+ soulMdPath,
236
+ registryDbPath: ':memory:',
237
+ creditDbPath: ':memory:',
238
+ gatewayPort: 0,
239
+ silent: true,
240
+ });
241
+
242
+ await deactivate(ctx);
243
+ // Second call must not throw
244
+ await expect(deactivate(ctx)).resolves.not.toThrow();
245
+
246
+ ctx = undefined;
247
+ });
248
+
249
+ // ---------------------------------------------------------------------------
250
+ // Test 8: activate() throws when SOUL.md does not exist
251
+ // ---------------------------------------------------------------------------
252
+ it('activate() throws when SOUL.md does not exist', async () => {
253
+ await expect(
254
+ activate({
255
+ owner: 'test-agent',
256
+ soulMdPath: '/nonexistent/path/SOUL.md',
257
+ registryDbPath: ':memory:',
258
+ creditDbPath: ':memory:',
259
+ gatewayPort: 0,
260
+ silent: true,
261
+ }),
262
+ ).rejects.toThrow();
263
+ });
264
+
265
+ // ---------------------------------------------------------------------------
266
+ // Test 9: activate() with identityRequired creates identity.json
267
+ // ---------------------------------------------------------------------------
268
+ it('activate() with identityRequired creates identity.json', async () => {
269
+ const soulMdPath = setupSoulMd();
270
+ const identityDir = mkdtempSync(join(tmpdir(), 'agentbnb-identity-'));
271
+
272
+ // Temporarily override HOME so identity writes to temp dir
273
+ const origHome = process.env['HOME'];
274
+ process.env['HOME'] = identityDir;
275
+
276
+ try {
277
+ ctx = await activate({
278
+ owner: 'identity-agent',
279
+ soulMdPath,
280
+ registryDbPath: ':memory:',
281
+ creditDbPath: ':memory:',
282
+ gatewayPort: 0,
283
+ silent: true,
284
+ identityRequired: true,
285
+ });
286
+
287
+ // identity should be populated
288
+ expect(ctx.identity).not.toBeNull();
289
+ expect(ctx.identity!.owner).toBe('identity-agent');
290
+ expect(ctx.identity!.agent_id).toBeTruthy();
291
+
292
+ // identity.json should exist on disk
293
+ const identityPath = join(identityDir, '.agentbnb', 'identity.json');
294
+ expect(existsSync(identityPath)).toBe(true);
295
+
296
+ const loaded = loadIdentity(join(identityDir, '.agentbnb'));
297
+ expect(loaded).not.toBeNull();
298
+ expect(loaded!.agent_id).toBe(ctx.identity!.agent_id);
299
+ } finally {
300
+ process.env['HOME'] = origHome;
301
+ rmSync(identityDir, { recursive: true, force: true });
302
+ }
303
+ });
304
+
305
+ // ---------------------------------------------------------------------------
306
+ // Test 10: activate() without identityRequired skips identity creation
307
+ // ---------------------------------------------------------------------------
308
+ it('activate() without identityRequired skips identity creation', async () => {
309
+ const soulMdPath = setupSoulMd();
310
+
311
+ ctx = await activate({
312
+ owner: 'test-agent',
313
+ soulMdPath,
314
+ registryDbPath: ':memory:',
315
+ creditDbPath: ':memory:',
316
+ gatewayPort: 0,
317
+ silent: true,
318
+ identityRequired: false,
319
+ });
320
+
321
+ expect(ctx.identity).toBeNull();
322
+ });
323
+ });
@@ -0,0 +1,126 @@
1
+ /**
2
+ * AgentBnB Bootstrap — single-call OpenClaw skill lifecycle entry point.
3
+ *
4
+ * Usage: `const ctx = await activate({ owner: 'alice', soulMdPath: './SOUL.md' });`
5
+ * Teardown: `await deactivate(ctx);`
6
+ */
7
+
8
+ import { existsSync, readFileSync } from 'node:fs';
9
+ import { randomUUID } from 'node:crypto';
10
+ import { homedir } from 'node:os';
11
+ import { join } from 'node:path';
12
+ import type { FastifyInstance } from 'fastify';
13
+
14
+ import { AgentRuntime } from '../../src/runtime/agent-runtime.js';
15
+ import { publishFromSoulV2 } from '../../src/openclaw/soul-sync.js';
16
+ import { createGatewayServer } from '../../src/gateway/server.js';
17
+ import { IdleMonitor } from '../../src/autonomy/idle-monitor.js';
18
+ import { DEFAULT_AUTONOMY_CONFIG } from '../../src/autonomy/tiers.js';
19
+ import { AgentBnBError } from '../../src/types/index.js';
20
+ import { ensureIdentity, type AgentIdentity } from '../../src/identity/identity.js';
21
+ import type { AutonomyConfig } from '../../src/autonomy/tiers.js';
22
+ import type { CapabilityCardV2 } from '../../src/types/index.js';
23
+
24
+ /** Configuration for bringing an AgentBnB agent online. */
25
+ export interface BootstrapConfig {
26
+ /** Agent owner identifier. */
27
+ owner: string;
28
+ /** Absolute path to SOUL.md. */
29
+ soulMdPath: string;
30
+ /** Registry DB path. Defaults to ~/.agentbnb/registry.db */
31
+ registryDbPath?: string;
32
+ /** Credit DB path. Defaults to ~/.agentbnb/credit.db */
33
+ creditDbPath?: string;
34
+ /** Gateway port. Defaults to 7700. */
35
+ gatewayPort?: number;
36
+ /** Bearer token for gateway auth. Defaults to a random UUID. */
37
+ gatewayToken?: string;
38
+ /** Handler URL for capability forwarding. Defaults to http://localhost:{gatewayPort}. */
39
+ handlerUrl?: string;
40
+ /** Autonomy tier config. Defaults to DEFAULT_AUTONOMY_CONFIG (Tier 3). */
41
+ autonomyConfig?: AutonomyConfig;
42
+ /** Suppress gateway logs. Defaults to false. */
43
+ silent?: boolean;
44
+ /** When true, ensures identity.json exists on activate. Defaults to true. */
45
+ identityRequired?: boolean;
46
+ }
47
+
48
+ /** Live handles returned by activate(). Pass to deactivate() for clean teardown. */
49
+ export interface BootstrapContext {
50
+ /** AgentRuntime managing DBs and background job lifecycle. */
51
+ runtime: AgentRuntime;
52
+ /** Fastify gateway HTTP server instance. */
53
+ gateway: FastifyInstance;
54
+ /** IdleMonitor background loop. */
55
+ idleMonitor: IdleMonitor;
56
+ /** Published CapabilityCard derived from SOUL.md. */
57
+ card: CapabilityCardV2;
58
+ /** Agent identity (created/loaded during activation). */
59
+ identity: AgentIdentity | null;
60
+ }
61
+
62
+ /**
63
+ * Brings an agent fully online: Runtime -> publishCard -> gateway.listen -> IdleMonitor.
64
+ * @throws {AgentBnBError} FILE_NOT_FOUND if SOUL.md does not exist.
65
+ */
66
+ export async function activate(config: BootstrapConfig): Promise<BootstrapContext> {
67
+ const {
68
+ owner,
69
+ soulMdPath,
70
+ registryDbPath = join(homedir(), '.agentbnb', 'registry.db'),
71
+ creditDbPath = join(homedir(), '.agentbnb', 'credit.db'),
72
+ gatewayPort = 7700,
73
+ gatewayToken = randomUUID(),
74
+ autonomyConfig = DEFAULT_AUTONOMY_CONFIG,
75
+ silent = false,
76
+ } = config;
77
+
78
+ const identityRequired = config.identityRequired ?? false;
79
+ const handlerUrl = config.handlerUrl ?? `http://localhost:${gatewayPort}`;
80
+
81
+ if (!existsSync(soulMdPath)) {
82
+ throw new AgentBnBError(`SOUL.md not found at path: ${soulMdPath}`, 'FILE_NOT_FOUND');
83
+ }
84
+ const soulContent = readFileSync(soulMdPath, 'utf8');
85
+
86
+ const runtime = new AgentRuntime({ registryDbPath, creditDbPath, owner });
87
+ await runtime.start();
88
+
89
+ // Ensure agent identity exists (idempotent — preserves existing identity)
90
+ let identity: AgentIdentity | null = null;
91
+ if (identityRequired) {
92
+ const configDir = join(homedir(), '.agentbnb');
93
+ identity = ensureIdentity(configDir, owner);
94
+ }
95
+
96
+ const card = publishFromSoulV2(runtime.registryDb, soulContent, owner);
97
+
98
+ const gateway = createGatewayServer({
99
+ port: gatewayPort,
100
+ registryDb: runtime.registryDb,
101
+ creditDb: runtime.creditDb,
102
+ tokens: [gatewayToken],
103
+ handlerUrl,
104
+ silent,
105
+ });
106
+ await gateway.listen({ port: gatewayPort, host: '0.0.0.0' });
107
+
108
+ const idleMonitor = new IdleMonitor({ owner, db: runtime.registryDb, autonomyConfig });
109
+ const idleJob = idleMonitor.start();
110
+ runtime.registerJob(idleJob);
111
+
112
+ return { runtime, gateway, idleMonitor, card, identity };
113
+ }
114
+
115
+ /**
116
+ * Tears down all active components: gateway.close() then runtime.shutdown().
117
+ * Idempotent — safe to call multiple times.
118
+ */
119
+ export async function deactivate(ctx: BootstrapContext): Promise<void> {
120
+ try {
121
+ await ctx.gateway.close();
122
+ await ctx.runtime.shutdown();
123
+ } catch {
124
+ // Swallow errors — idempotent teardown
125
+ }
126
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * AgentBnB Credit Management adapter for OpenClaw skill integration.
3
+ * Delegates to BudgetManager and getBalance from src/credit/.
4
+ * This is a thin re-export wrapper — no business logic lives here.
5
+ */
6
+ import { BudgetManager, DEFAULT_BUDGET_CONFIG } from '../../src/credit/budget.js';
7
+ import { getBalance } from '../../src/credit/ledger.js';
8
+ import type { BudgetConfig } from '../../src/credit/budget.js';
9
+
10
+ export { BudgetManager, DEFAULT_BUDGET_CONFIG, getBalance };
11
+ export type { BudgetConfig };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * AgentBnB Gateway adapter for OpenClaw skill integration.
3
+ * Delegates to AgentRuntime and createGatewayServer from src/.
4
+ * This is a thin re-export wrapper — no business logic lives here.
5
+ */
6
+ import { AgentRuntime } from '../../src/runtime/agent-runtime.js';
7
+ import { createGatewayServer } from '../../src/gateway/server.js';
8
+ import type { RuntimeOptions } from '../../src/runtime/agent-runtime.js';
9
+ import type { GatewayOptions } from '../../src/gateway/server.js';
10
+
11
+ export { AgentRuntime, createGatewayServer };
12
+ export type { RuntimeOptions, GatewayOptions };