@sage-protocol/openclaw-sage 0.1.2 → 0.1.3
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/.github/workflows/release-please.yml +18 -0
- package/.release-please-manifest.json +3 -0
- package/CHANGELOG.md +20 -0
- package/README.md +20 -13
- package/SOUL.md +62 -0
- package/package.json +2 -2
- package/release-please-config.json +13 -0
- package/src/index.ts +142 -0
- package/src/mcp-bridge.test.ts +81 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: release-please
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
pull-requests: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release-please:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: googleapis/release-please-action@v4
|
|
16
|
+
with:
|
|
17
|
+
config-file: release-please-config.json
|
|
18
|
+
manifest-file: .release-please-manifest.json
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.3](https://github.com/sage-protocol/openclaw-sage/compare/openclaw-sage-v0.1.2...openclaw-sage-v0.1.3) (2026-02-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add Sage capture hooks ([f8fab39](https://github.com/sage-protocol/openclaw-sage/commit/f8fab399860de55d1949c9358a443372f0617eb6))
|
|
9
|
+
* adding ci, release please and improvements ([c32f79a](https://github.com/sage-protocol/openclaw-sage/commit/c32f79a05ee9212d4e382c9131ec684c91706add))
|
|
10
|
+
* adding readme ([4aea6c9](https://github.com/sage-protocol/openclaw-sage/commit/4aea6c950e2dcf276ebcb492cfe70b6ac4cd138e))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* fixing manifest naming ([28d6add](https://github.com/sage-protocol/openclaw-sage/commit/28d6add05e0b4e60b17993aaf73a23ef55ab1c94))
|
|
16
|
+
* fixing missing openclaw manifest ([5062ae7](https://github.com/sage-protocol/openclaw-sage/commit/5062ae789d2733a209ce2f0c63453779f73245fb))
|
|
17
|
+
|
|
18
|
+
## Changelog
|
|
19
|
+
|
|
20
|
+
All notable changes to this package are documented here.
|
package/README.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Sage Plugin (OpenClaw)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP bridge plugin that exposes all Sage Protocol tools inside OpenClaw. Spawns the sage MCP server as a child process and translates JSON-RPC calls into registered OpenClaw tools.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
- **MCP Tool Bridge** - Spawns `sage mcp start` and translates JSON-RPC tool calls into native OpenClaw tools
|
|
8
|
+
- **Dynamic Registration** - Discovers available tools at startup and registers them with typed schemas
|
|
9
|
+
- **RLM Capture** - Records prompt/response pairs for Sage's RLM feedback loop
|
|
10
|
+
- **Crash Recovery** - Automatically restarts the MCP subprocess on unexpected exits
|
|
4
11
|
|
|
5
12
|
## Install
|
|
6
13
|
|
|
@@ -8,11 +15,6 @@ Sage Protocol MCP bridge plugin for OpenClaw. Provides prompt libraries, skills,
|
|
|
8
15
|
openclaw plugins install @sage-protocol/openclaw-sage
|
|
9
16
|
```
|
|
10
17
|
|
|
11
|
-
## Requirements
|
|
12
|
-
|
|
13
|
-
- [Sage CLI](https://github.com/sage-protocol/sage-cli) installed and available on PATH
|
|
14
|
-
- OpenClaw v0.1.0+
|
|
15
|
-
|
|
16
18
|
## Configuration
|
|
17
19
|
|
|
18
20
|
The plugin auto-detects the `sage` binary from PATH. To override:
|
|
@@ -25,13 +27,18 @@ The plugin auto-detects the `sage` binary from PATH. To override:
|
|
|
25
27
|
|
|
26
28
|
## What It Provides
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
Once loaded, all Sage MCP tools are available in OpenClaw:
|
|
31
|
+
|
|
32
|
+
- **Prompts & Libraries** - Search, list, create, and manage prompt libraries
|
|
33
|
+
- **Skills** - Discover and activate skills from Sage Protocol, GitHub, or local sources
|
|
34
|
+
- **Builder** - AI-powered prompt recommendations and synthesis
|
|
35
|
+
- **Governance** - List DAOs, view proposals, check voting power
|
|
36
|
+
- **Hub** - Start/stop additional MCP servers (memory, brave-search, github, etc.)
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
- **Hub** — start/stop additional MCP servers (memory, brave-search, github, etc.)
|
|
38
|
+
## Requirements
|
|
39
|
+
|
|
40
|
+
- Sage CLI on PATH
|
|
41
|
+
- OpenClaw v0.1.0+
|
|
35
42
|
|
|
36
43
|
## Development
|
|
37
44
|
|
package/SOUL.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# OpenClaw Agent SOUL
|
|
2
|
+
|
|
3
|
+
This file defines the default behavior for an OpenClaw agent operating in the Sage Protocol ecosystem.
|
|
4
|
+
|
|
5
|
+
## Core Objective
|
|
6
|
+
|
|
7
|
+
Help the user get productive with Sage as fast as possible by reliably steering onboarding toward:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
sage init
|
|
11
|
+
sage agent quickstart
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## When To Prompt For Onboarding
|
|
15
|
+
|
|
16
|
+
Prompt the user to run `sage init` and/or `sage agent quickstart` when any of the following is true:
|
|
17
|
+
|
|
18
|
+
- The user asks about setup, installation, configuration, MCP tools, skills, libraries, or OpenClaw integration.
|
|
19
|
+
- The session appears to be a first run (no prior mention of `sage init` / `sage agent quickstart`).
|
|
20
|
+
- The user hits auth/credits/wallet friction (examples: wallet not configured, faucet needed, IPFS credits missing, library sync failing).
|
|
21
|
+
|
|
22
|
+
## How To Prompt (Non-Spammy)
|
|
23
|
+
|
|
24
|
+
- Ask once, early, and only repeat if the user is blocked by missing setup.
|
|
25
|
+
- Prefer one short message with a single actionable next step.
|
|
26
|
+
- Don't derail the task: if the user is already mid-task, suggest onboarding as a quick fix only when it unblocks progress.
|
|
27
|
+
|
|
28
|
+
## Recommended Message Templates
|
|
29
|
+
|
|
30
|
+
If Sage is not initialized:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
Run `sage init` to set up the Sage skill + MCP integration for your environment.
|
|
34
|
+
After that, run `sage agent quickstart` to claim testnet tokens + IPFS credits.
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
If Sage is initialized but the user needs tokens/credits:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
Run `sage agent quickstart` to claim testnet tokens + IPFS credits (it will also check your current status).
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If the user is using OpenClaw and doesn't have MCP tools:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
Install the OpenClaw MCP bridge: `openclaw plugins install @sage-protocol/openclaw-sage`.
|
|
47
|
+
Then run: `sage init` and `sage agent quickstart`.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## OpenClaw Skill Directory
|
|
51
|
+
|
|
52
|
+
OpenClaw loads skills from:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
~/.openclaw/workspace/skills/
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Guardrails
|
|
59
|
+
|
|
60
|
+
- Don't instruct `git push`, key export, or any destructive command unless the user explicitly asks.
|
|
61
|
+
- Don't ask the user to paste secrets (tokens, private keys). Use `sage wallet connect -w privy` (OAuth) or documented wallet flows.
|
|
62
|
+
- Prefer the unified `sage suggest ...` family for discovery, but onboarding comes first.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sage-protocol/openclaw-sage",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Sage MCP bridge plugin for OpenClaw — prompt libraries, skills, governance, and on-chain operations",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"typecheck": "tsc --noEmit",
|
|
14
|
-
"test": "
|
|
14
|
+
"test": "npx --yes tsx src/mcp-bridge.test.ts"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@sinclair/typebox": "^0.34.0"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
|
3
|
+
"release-type": "node",
|
|
4
|
+
"include-v-in-tag": true,
|
|
5
|
+
"bump-minor-pre-major": true,
|
|
6
|
+
"bump-patch-for-minor-pre-major": true,
|
|
7
|
+
"packages": {
|
|
8
|
+
".": {
|
|
9
|
+
"package-name": "@sage-protocol/openclaw-sage",
|
|
10
|
+
"changelog-path": "CHANGELOG.md"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
|
|
3
5
|
import { McpBridge, type McpToolDef } from "./mcp-bridge.js";
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -100,6 +102,88 @@ function toToolResult(mcpResult: unknown) {
|
|
|
100
102
|
|
|
101
103
|
let bridge: McpBridge | null = null;
|
|
102
104
|
|
|
105
|
+
function extractText(input: unknown): string {
|
|
106
|
+
if (typeof input === "string") return input;
|
|
107
|
+
if (!input || typeof input !== "object") return "";
|
|
108
|
+
|
|
109
|
+
const obj = input as any;
|
|
110
|
+
const direct = [obj.text, obj.content, obj.prompt, obj.message, obj.input];
|
|
111
|
+
for (const c of direct) {
|
|
112
|
+
if (typeof c === "string" && c.trim()) return c.trim();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (obj.message && typeof obj.message === "object") {
|
|
116
|
+
const msg = obj.message as any;
|
|
117
|
+
if (typeof msg.text === "string" && msg.text.trim()) return msg.text.trim();
|
|
118
|
+
if (typeof msg.content === "string" && msg.content.trim()) return msg.content.trim();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Transcript-style: [{role, content}]
|
|
122
|
+
if (Array.isArray(obj.messages)) {
|
|
123
|
+
for (let i = obj.messages.length - 1; i >= 0; i--) {
|
|
124
|
+
const m = obj.messages[i];
|
|
125
|
+
if (!m || typeof m !== "object") continue;
|
|
126
|
+
const mm = m as any;
|
|
127
|
+
if (typeof mm.content === "string" && mm.content.trim()) return mm.content.trim();
|
|
128
|
+
if (typeof mm.text === "string" && mm.text.trim()) return mm.text.trim();
|
|
129
|
+
if (Array.isArray(mm.content)) {
|
|
130
|
+
const text = mm.content
|
|
131
|
+
.map((b: any) => (b?.type === "text" ? b?.text : ""))
|
|
132
|
+
.filter(Boolean)
|
|
133
|
+
.join("\n");
|
|
134
|
+
if (text.trim()) return text.trim();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return "";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function lastAssistantText(input: unknown): string {
|
|
143
|
+
if (!input || typeof input !== "object") return "";
|
|
144
|
+
const obj = input as any;
|
|
145
|
+
|
|
146
|
+
const candidates = [obj.final, obj.output, obj.result, obj.response];
|
|
147
|
+
for (const c of candidates) {
|
|
148
|
+
const t = extractText(c);
|
|
149
|
+
if (t) return t;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (Array.isArray(obj.messages)) {
|
|
153
|
+
for (let i = obj.messages.length - 1; i >= 0; i--) {
|
|
154
|
+
const m = obj.messages[i];
|
|
155
|
+
if (!m || typeof m !== "object") continue;
|
|
156
|
+
const mm = m as any;
|
|
157
|
+
if (mm.role === "assistant") {
|
|
158
|
+
const t = extractText(mm);
|
|
159
|
+
if (t) return t;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return "";
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function toStringOrEmpty(v: unknown): string {
|
|
168
|
+
if (typeof v === "string") return v;
|
|
169
|
+
if (typeof v === "number") return String(v);
|
|
170
|
+
if (typeof v === "boolean") return v ? "1" : "0";
|
|
171
|
+
return "";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function fireAndForget(cmd: string, args: string[], env: Record<string, string>): void {
|
|
175
|
+
try {
|
|
176
|
+
const p = spawn(cmd, args, {
|
|
177
|
+
env: { ...process.env, ...env },
|
|
178
|
+
stdio: "ignore",
|
|
179
|
+
detached: true,
|
|
180
|
+
});
|
|
181
|
+
p.unref();
|
|
182
|
+
} catch {
|
|
183
|
+
// ignore
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
103
187
|
const plugin = {
|
|
104
188
|
id: "openclaw-sage",
|
|
105
189
|
name: "Sage Protocol",
|
|
@@ -113,6 +197,64 @@ const plugin = {
|
|
|
113
197
|
bridge.on("log", (line: string) => api.logger.info(`[sage-mcp] ${line}`));
|
|
114
198
|
bridge.on("error", (err: Error) => api.logger.error(`[sage-mcp] ${err.message}`));
|
|
115
199
|
|
|
200
|
+
// RLM capture hooks (derived data): attach prompt/response pairs with OpenClaw tags.
|
|
201
|
+
api.on("message_received", async (evt: unknown) => {
|
|
202
|
+
const prompt = extractText(evt);
|
|
203
|
+
if (!prompt) return;
|
|
204
|
+
|
|
205
|
+
const e = evt as any;
|
|
206
|
+
const sessionId =
|
|
207
|
+
toStringOrEmpty(e?.sessionId) ||
|
|
208
|
+
toStringOrEmpty(e?.sessionKey) ||
|
|
209
|
+
toStringOrEmpty(e?.context?.sessionId) ||
|
|
210
|
+
toStringOrEmpty(e?.context?.sessionKey);
|
|
211
|
+
const workspaceDir = toStringOrEmpty(e?.workspaceDir) || toStringOrEmpty(e?.context?.workspaceDir);
|
|
212
|
+
|
|
213
|
+
const attrs = {
|
|
214
|
+
openclaw: {
|
|
215
|
+
hook: "message_received",
|
|
216
|
+
sessionId: toStringOrEmpty(e?.sessionId) || toStringOrEmpty(e?.context?.sessionId),
|
|
217
|
+
sessionKey: toStringOrEmpty(e?.sessionKey) || toStringOrEmpty(e?.context?.sessionKey),
|
|
218
|
+
channel: toStringOrEmpty(e?.channel) || toStringOrEmpty(e?.context?.commandSource),
|
|
219
|
+
senderId: toStringOrEmpty(e?.senderId) || toStringOrEmpty(e?.context?.senderId),
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
fireAndForget("sage", ["capture", "hook", "prompt"], {
|
|
224
|
+
SAGE_SOURCE: "openclaw",
|
|
225
|
+
OPENCLAW: "1",
|
|
226
|
+
PROMPT: prompt,
|
|
227
|
+
SAGE_SESSION_ID: sessionId,
|
|
228
|
+
SAGE_WORKSPACE: workspaceDir,
|
|
229
|
+
SAGE_MODEL: toStringOrEmpty(e?.model) || toStringOrEmpty(e?.context?.model),
|
|
230
|
+
SAGE_PROVIDER: toStringOrEmpty(e?.provider) || toStringOrEmpty(e?.context?.provider),
|
|
231
|
+
SAGE_CAPTURE_ATTRIBUTES_JSON: JSON.stringify(attrs),
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
api.on("agent_end", async (evt: unknown) => {
|
|
236
|
+
const response = lastAssistantText(evt);
|
|
237
|
+
if (!response) return;
|
|
238
|
+
|
|
239
|
+
const e = evt as any;
|
|
240
|
+
const tokensIn =
|
|
241
|
+
toStringOrEmpty(e?.usage?.tokens_input) ||
|
|
242
|
+
toStringOrEmpty(e?.usage?.input_tokens) ||
|
|
243
|
+
toStringOrEmpty(e?.usage?.inputTokens);
|
|
244
|
+
const tokensOut =
|
|
245
|
+
toStringOrEmpty(e?.usage?.tokens_output) ||
|
|
246
|
+
toStringOrEmpty(e?.usage?.output_tokens) ||
|
|
247
|
+
toStringOrEmpty(e?.usage?.outputTokens);
|
|
248
|
+
|
|
249
|
+
fireAndForget("sage", ["capture", "hook", "response"], {
|
|
250
|
+
SAGE_SOURCE: "openclaw",
|
|
251
|
+
OPENCLAW: "1",
|
|
252
|
+
LAST_RESPONSE: response,
|
|
253
|
+
TOKENS_INPUT: tokensIn,
|
|
254
|
+
TOKENS_OUTPUT: tokensOut,
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
116
258
|
api.registerService({
|
|
117
259
|
id: "sage-mcp-bridge",
|
|
118
260
|
start: async (ctx) => {
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { McpBridge } from "./mcp-bridge.js";
|
|
6
|
+
import plugin from "./index.js";
|
|
7
|
+
|
|
8
|
+
function addSageDebugBinToPath() {
|
|
9
|
+
// Ensure the `sage` binary used by the plugin resolves to this repo's build.
|
|
10
|
+
const binDir = resolve(new URL("..", import.meta.url).pathname, "..", "target", "debug");
|
|
11
|
+
const sep = process.platform === "win32" ? ";" : ":";
|
|
12
|
+
process.env.PATH = `${binDir}${sep}${process.env.PATH ?? ""}`;
|
|
13
|
+
return { binDir };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test("McpBridge can initialize, list tools, and call a native tool", async () => {
|
|
17
|
+
const sageBin = resolve(new URL("..", import.meta.url).pathname, "..", "target", "debug", "sage");
|
|
18
|
+
const bridge = new McpBridge(sageBin, ["mcp", "start"]);
|
|
19
|
+
await bridge.start();
|
|
20
|
+
try {
|
|
21
|
+
const tools = await bridge.listTools();
|
|
22
|
+
assert.ok(Array.isArray(tools));
|
|
23
|
+
assert.ok(tools.length > 0);
|
|
24
|
+
|
|
25
|
+
const hasProjectContext = tools.some((t) => t.name === "get_project_context");
|
|
26
|
+
assert.ok(hasProjectContext, "expected get_project_context tool to exist");
|
|
27
|
+
|
|
28
|
+
const result = await bridge.callTool("get_project_context", {});
|
|
29
|
+
assert.ok(result && typeof result === "object");
|
|
30
|
+
} finally {
|
|
31
|
+
await bridge.stop();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("OpenClaw plugin registers MCP tools via sage mcp start", async () => {
|
|
36
|
+
addSageDebugBinToPath();
|
|
37
|
+
|
|
38
|
+
const registeredTools: string[] = [];
|
|
39
|
+
const services: Array<{ id: string; start: Function; stop?: Function }> = [];
|
|
40
|
+
|
|
41
|
+
const api = {
|
|
42
|
+
id: "t",
|
|
43
|
+
name: "t",
|
|
44
|
+
logger: {
|
|
45
|
+
info: (_: string) => {},
|
|
46
|
+
warn: (_: string) => {},
|
|
47
|
+
error: (_: string) => {},
|
|
48
|
+
},
|
|
49
|
+
registerTool: (tool: any) => {
|
|
50
|
+
if (tool?.name) registeredTools.push(tool.name);
|
|
51
|
+
},
|
|
52
|
+
registerService: (svc: any) => {
|
|
53
|
+
services.push(svc);
|
|
54
|
+
},
|
|
55
|
+
on: (_hook: string, _handler: any) => {},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
plugin.register(api);
|
|
59
|
+
const svc = services.find((s) => s.id === "sage-mcp-bridge");
|
|
60
|
+
assert.ok(svc, "expected sage-mcp-bridge service to be registered");
|
|
61
|
+
|
|
62
|
+
await svc!.start({
|
|
63
|
+
config: {},
|
|
64
|
+
stateDir: "/tmp",
|
|
65
|
+
logger: api.logger,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Tool names are prefixed with `sage_` in this plugin.
|
|
69
|
+
assert.ok(
|
|
70
|
+
registeredTools.some((n) => n.startsWith("sage_")),
|
|
71
|
+
"expected at least one sage_* tool",
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (svc!.stop) {
|
|
75
|
+
await svc!.stop({
|
|
76
|
+
config: {},
|
|
77
|
+
stateDir: "/tmp",
|
|
78
|
+
logger: api.logger,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|