agentbnb 4.0.1 → 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.
- package/README.md +2 -0
- package/dist/{card-4XH4AOTE.js → card-RSGDCHCV.js} +1 -1
- package/dist/{chunk-MQKYGY5I.js → chunk-4P3EMGL4.js} +3 -3
- package/dist/{chunk-DVAS2443.js → chunk-5KFI5X7B.js} +1 -1
- package/dist/{chunk-Q7HRI666.js → chunk-7NA43XCG.js} +4 -4
- package/dist/{chunk-3UKAVIMC.js → chunk-BH6WGYFB.js} +4 -4
- package/dist/{chunk-XQHN6ITI.js → chunk-DNWT5FZQ.js} +22 -2
- package/dist/{chunk-QJEOCKVF.js → chunk-FF226TIV.js} +1 -1
- package/dist/{chunk-6K5WUVF3.js → chunk-GGYC5U2Z.js} +4 -4
- package/dist/{chunk-ODBGCCEH.js → chunk-HH24WMFN.js} +18 -3
- package/dist/{chunk-M3G5NR2Z.js → chunk-QITOPASZ.js} +8 -2
- package/dist/{chunk-TLU7ALCZ.js → chunk-T7NS2J2B.js} +1 -1
- package/dist/{chunk-FNKBHBYK.js → chunk-WGZ5AGOX.js} +37 -3
- package/dist/{chunk-KJG2UJV5.js → chunk-XND2DWTZ.js} +3 -2
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +403 -134
- package/dist/{client-BTPIFY7E.js → client-T5MTY3CS.js} +3 -3
- package/dist/conduct-GZQNFTRP.js +19 -0
- package/dist/{conduct-CW62HBPT.js → conduct-N52JX7RT.js} +9 -9
- package/dist/{conductor-mode-3JS4VWCR.js → conductor-mode-XUWGR4ZE.js} +7 -7
- package/dist/execute-PNGQOMYO.js +10 -0
- package/dist/index.d.ts +146 -2
- package/dist/index.js +131 -65
- package/dist/{request-CNZ3XIVX.js → request-4GQSSM4B.js} +8 -8
- package/dist/{serve-skill-SUOGUM7N.js → serve-skill-TPHZH6BS.js} +5 -5
- package/dist/{server-2LWHL24P.js → server-365V3GYD.js} +10 -10
- package/package.json +3 -6
- package/skills/agentbnb/HEARTBEAT.rules.md +47 -0
- package/skills/agentbnb/SKILL.md +166 -0
- package/skills/agentbnb/auto-request.ts +14 -0
- package/skills/agentbnb/auto-share.ts +10 -0
- package/skills/agentbnb/bootstrap.test.ts +323 -0
- package/skills/agentbnb/bootstrap.ts +126 -0
- package/skills/agentbnb/credit-mgr.ts +11 -0
- package/skills/agentbnb/gateway.ts +12 -0
- package/skills/agentbnb/install.sh +210 -0
- package/dist/conduct-FXLVGKD5.js +0 -19
- package/dist/execute-EXOITLHN.js +0 -10
- package/dist/types-FGBUZ3QV.js +0 -18
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ensureIdentity
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QITOPASZ.js";
|
|
4
4
|
import {
|
|
5
5
|
createLedger
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-HH24WMFN.js";
|
|
7
7
|
import {
|
|
8
8
|
fetchRemoteCards,
|
|
9
9
|
mergeResults,
|
|
10
10
|
searchCards
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-FF226TIV.js";
|
|
12
12
|
import {
|
|
13
13
|
getConfigDir,
|
|
14
14
|
loadConfig
|
|
@@ -16,17 +16,17 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
insertCard,
|
|
18
18
|
openDatabase
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-T7NS2J2B.js";
|
|
20
20
|
import {
|
|
21
21
|
getBalance,
|
|
22
22
|
openCreditDb
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-DNWT5FZQ.js";
|
|
24
24
|
import {
|
|
25
25
|
loadKeyPair
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-5KFI5X7B.js";
|
|
27
27
|
import {
|
|
28
28
|
AnyCardSchema
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-WGZ5AGOX.js";
|
|
30
30
|
|
|
31
31
|
// src/mcp/server.ts
|
|
32
32
|
import { createRequire } from "module";
|
|
@@ -273,9 +273,9 @@ async function startMcpServer() {
|
|
|
273
273
|
registerDiscoverTool(server, ctx);
|
|
274
274
|
registerStatusTool(server, ctx);
|
|
275
275
|
registerPublishTool(server, ctx);
|
|
276
|
-
const { registerRequestTool } = await import("./request-
|
|
277
|
-
const { registerConductTool } = await import("./conduct-
|
|
278
|
-
const { registerServeSkillTool } = await import("./serve-skill-
|
|
276
|
+
const { registerRequestTool } = await import("./request-4GQSSM4B.js");
|
|
277
|
+
const { registerConductTool } = await import("./conduct-N52JX7RT.js");
|
|
278
|
+
const { registerServeSkillTool } = await import("./serve-skill-TPHZH6BS.js");
|
|
279
279
|
registerRequestTool(server, ctx);
|
|
280
280
|
registerConductTool(server, ctx);
|
|
281
281
|
registerServeSkillTool(server, ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentbnb",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.2",
|
|
4
4
|
"description": "P2P Agent Capability Sharing Protocol — Airbnb for AI agent pipelines",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
13
|
"README.md",
|
|
14
|
-
"LICENSE"
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"skills"
|
|
15
16
|
],
|
|
16
17
|
"exports": {
|
|
17
18
|
".": {
|
|
@@ -25,10 +26,6 @@
|
|
|
25
26
|
"./identity": {
|
|
26
27
|
"types": "./dist/identity/index.d.ts",
|
|
27
28
|
"import": "./dist/identity/index.js"
|
|
28
|
-
},
|
|
29
|
-
"./adapters/claude-code": {
|
|
30
|
-
"types": "./dist/adapters/claude-code/index.d.ts",
|
|
31
|
-
"import": "./dist/adapters/claude-code/index.js"
|
|
32
29
|
}
|
|
33
30
|
},
|
|
34
31
|
"scripts": {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# AgentBnB Autonomy Rules — HEARTBEAT.md Template (v3.0)
|
|
2
|
+
|
|
3
|
+
This file contains the autonomy rules block for the AgentBnB OpenClaw skill.
|
|
4
|
+
|
|
5
|
+
**How to use:**
|
|
6
|
+
- Copy the content between the markers directly into your `HEARTBEAT.md` file, OR
|
|
7
|
+
- Run `agentbnb openclaw rules` to generate a rules block with your *actual configured thresholds*
|
|
8
|
+
|
|
9
|
+
**Note:** The values shown below are example defaults (Tier 1: 10 credits, Tier 2: 50 credits,
|
|
10
|
+
reserve: 20 credits). The `agentbnb openclaw rules` command outputs rules using the owner's
|
|
11
|
+
real configured thresholds from `~/.agentbnb/config.json`.
|
|
12
|
+
|
|
13
|
+
**Identity:** On activation, the agent's Ed25519 identity is verified via `~/.agentbnb/identity.json`.
|
|
14
|
+
This identity is used for escrow receipt signing and P2P verification.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<!-- agentbnb:start -->
|
|
19
|
+
## AgentBnB Protocol
|
|
20
|
+
|
|
21
|
+
You are a participant in the AgentBnB capability sharing network.
|
|
22
|
+
|
|
23
|
+
### Sharing Rules
|
|
24
|
+
- When your idle_rate exceeds 70%, automatically share idle capabilities
|
|
25
|
+
- Accept incoming requests that match your published Capability Card
|
|
26
|
+
- Track credit earnings and report to owner weekly
|
|
27
|
+
|
|
28
|
+
### Requesting Rules
|
|
29
|
+
- When you encounter a task you cannot complete with local skills:
|
|
30
|
+
1. Query AgentBnB network for matching capabilities
|
|
31
|
+
2. If found and credit sufficient, automatically request
|
|
32
|
+
3. Integrate result into your current workflow
|
|
33
|
+
- Budget limits:
|
|
34
|
+
- < 10 credits: auto-execute, no notification (Tier 1)
|
|
35
|
+
- 10-50 credits: execute, notify owner after (Tier 2)
|
|
36
|
+
- > 50 credits: ask owner before executing (Tier 3)
|
|
37
|
+
|
|
38
|
+
### Credit Management
|
|
39
|
+
- Maintain minimum balance of 20 credits (reserve for emergencies)
|
|
40
|
+
- If balance drops below 20, increase sharing priority
|
|
41
|
+
- If balance exceeds 500, notify owner of surplus
|
|
42
|
+
|
|
43
|
+
### Autonomy Configuration
|
|
44
|
+
- Default: Tier 3 (ask before all actions) until owner configures thresholds
|
|
45
|
+
- Configure via: `agentbnb config set tier1 <credits>` and `agentbnb config set tier2 <credits>`
|
|
46
|
+
- Reserve floor: `agentbnb config set reserve <credits>` (default 20)
|
|
47
|
+
<!-- agentbnb:end -->
|
|
@@ -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
|
+
});
|